diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendContent.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendContent.kt index 139ce26c85..dd99211634 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendContent.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendContent.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag @@ -15,17 +14,15 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.bitwarden.ui.platform.base.util.standardHorizontalMargin import com.bitwarden.ui.platform.base.util.toListItemCardStyle -import com.bitwarden.ui.platform.components.card.BitwardenActionCard import com.bitwarden.ui.platform.components.card.BitwardenInfoCalloutCard import com.bitwarden.ui.platform.components.header.BitwardenListHeaderText import com.bitwarden.ui.platform.components.icon.model.IconData import com.bitwarden.ui.platform.components.model.CardStyle -import com.bitwarden.ui.platform.components.util.rememberVectorPainter import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString -import com.bitwarden.ui.platform.theme.BitwardenTheme import com.x8bit.bitwarden.ui.platform.components.listitem.BitwardenGroupItem import com.x8bit.bitwarden.ui.tools.feature.send.handlers.SendHandlers +import com.x8bit.bitwarden.ui.tools.feature.send.model.UpgradedToPremiumCardData private const val SEND_TYPES_COUNT: Int = 2 @@ -37,39 +34,24 @@ private const val SEND_TYPES_COUNT: Int = 2 fun SendContent( policyDisablesSend: Boolean, state: SendState.ViewState.Content, - isUpgradedToPremiumCardEligible: Boolean, + upgradedToPremiumCardData: UpgradedToPremiumCardData?, sendHandlers: SendHandlers, - onUpgradedToPremiumCardClick: () -> Unit, - onUpgradedToPremiumCardDismiss: () -> Unit, modifier: Modifier = Modifier, ) { LazyColumn(modifier = modifier) { item { Spacer(modifier = Modifier.height(height = 12.dp)) } - if (isUpgradedToPremiumCardEligible) { + upgradedToPremiumCardData?.let { item { - BitwardenActionCard( - cardTitle = stringResource(id = BitwardenString.upgraded_to_premium), - cardSubtitle = stringResource( - id = BitwardenString.you_now_have_access_to_all_advanced_security_features, - ), - actionText = stringResource(id = BitwardenString.learn_more), - isExternalLink = true, - leadingContent = { - Icon( - painter = rememberVectorPainter(id = BitwardenDrawable.ic_star), - contentDescription = null, - tint = BitwardenTheme.colorScheme.icon.secondary, - ) - }, - onActionClick = onUpgradedToPremiumCardClick, - onDismissClick = onUpgradedToPremiumCardDismiss, + UpgradedToPremiumActionCard( + onActionClick = it.onCardClick, + onDismissClick = it.onCardDismiss, modifier = Modifier .fillMaxWidth() .standardHorizontalMargin(), ) - Spacer(modifier = Modifier.height(height = 12.dp)) + Spacer(modifier = Modifier.height(height = 16.dp)) } } if (policyDisablesSend) { diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendEmpty.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendEmpty.kt index 4f5716d574..579f741d69 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendEmpty.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendEmpty.kt @@ -31,6 +31,7 @@ import com.bitwarden.ui.platform.components.util.rememberVectorPainter import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.platform.theme.BitwardenTheme +import com.x8bit.bitwarden.ui.tools.feature.send.model.UpgradedToPremiumCardData /** * Content for the empty state of the [SendScreen]. @@ -40,12 +41,23 @@ import com.bitwarden.ui.platform.theme.BitwardenTheme fun SendEmpty( policyDisablesSend: Boolean, onAddItemClick: () -> Unit, + upgradedToPremiumCardData: UpgradedToPremiumCardData?, modifier: Modifier = Modifier, ) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier.verticalScroll(rememberScrollState()), ) { + upgradedToPremiumCardData?.let { + Spacer(modifier = Modifier.height(height = 12.dp)) + UpgradedToPremiumActionCard( + onActionClick = it.onCardClick, + onDismissClick = it.onCardDismiss, + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } if (policyDisablesSend) { Spacer(modifier = Modifier.height(12.dp)) BitwardenInfoCalloutCard( @@ -119,6 +131,7 @@ private fun SendEmpty_preview() { SendEmpty( policyDisablesSend = false, onAddItemClick = {}, + upgradedToPremiumCardData = null, ) } } @@ -135,6 +148,7 @@ private fun SendEmptyPolicyDisabled_preview() { SendEmpty( policyDisablesSend = true, onAddItemClick = {}, + upgradedToPremiumCardData = null, ) } } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendScreen.kt index 27e7b9514f..34515e76f2 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable @@ -23,6 +24,7 @@ import com.bitwarden.ui.platform.components.appbar.action.BitwardenOverflowActio import com.bitwarden.ui.platform.components.appbar.action.BitwardenSearchActionItem import com.bitwarden.ui.platform.components.appbar.model.OverflowMenuItemData import com.bitwarden.ui.platform.components.button.model.BitwardenButtonData +import com.bitwarden.ui.platform.components.card.BitwardenActionCard import com.bitwarden.ui.platform.components.content.BitwardenErrorContent import com.bitwarden.ui.platform.components.content.BitwardenLoadingContent import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog @@ -39,6 +41,7 @@ import com.bitwarden.ui.platform.composition.LocalIntentManager import com.bitwarden.ui.platform.manager.IntentManager import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString +import com.bitwarden.ui.platform.theme.BitwardenTheme import com.bitwarden.ui.util.asText import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData import com.x8bit.bitwarden.data.platform.manager.util.AppResumeStateManager @@ -49,6 +52,7 @@ import com.x8bit.bitwarden.ui.tools.feature.send.addedit.AddEditSendRoute import com.x8bit.bitwarden.ui.tools.feature.send.addedit.ModeType import com.x8bit.bitwarden.ui.tools.feature.send.handlers.SendHandlers import com.x8bit.bitwarden.ui.tools.feature.send.model.SendItemType +import com.x8bit.bitwarden.ui.tools.feature.send.model.UpgradedToPremiumCardData import com.x8bit.bitwarden.ui.tools.feature.send.util.selectionText import com.x8bit.bitwarden.ui.tools.feature.send.viewsend.ViewSendRoute import kotlinx.collections.immutable.persistentListOf @@ -181,24 +185,27 @@ fun SendScreen( snackbarHost = { BitwardenSnackbarHost(bitwardenHostState = snackbarHostState) }, ) { val contentModifier = Modifier.fillMaxSize() + val upgradedToPremiumCardData = UpgradedToPremiumCardData( + onCardClick = { + viewModel.trySendAction(SendAction.UpgradedToPremiumCardClick) + }, + onCardDismiss = { + viewModel.trySendAction(SendAction.UpgradedToPremiumCardDismiss) + }, + ).takeIf { state.isUpgradedToPremiumCardEligible } when (val viewState = state.viewState) { is SendState.ViewState.Content -> SendContent( policyDisablesSend = state.policyDisablesSend, state = viewState, - isUpgradedToPremiumCardEligible = state.isUpgradedToPremiumCardEligible, + upgradedToPremiumCardData = upgradedToPremiumCardData, sendHandlers = sendHandlers, - onUpgradedToPremiumCardClick = { - viewModel.trySendAction(SendAction.UpgradedToPremiumCardClick) - }, - onUpgradedToPremiumCardDismiss = { - viewModel.trySendAction(SendAction.UpgradedToPremiumCardDismiss) - }, modifier = contentModifier, ) SendState.ViewState.Empty -> SendEmpty( policyDisablesSend = state.policyDisablesSend, onAddItemClick = { viewModel.trySendAction(SendAction.AddSendClick) }, + upgradedToPremiumCardData = upgradedToPremiumCardData, modifier = contentModifier, ) @@ -251,3 +258,33 @@ private fun SendDialogs( null -> Unit } } + +/** + * Action card rendered at the top of the Send list when the user has just upgraded to premium. + * Owned by the screen so it can be hosted inside each view state's scrollable container. + */ +@Composable +internal fun UpgradedToPremiumActionCard( + onActionClick: () -> Unit, + onDismissClick: () -> Unit, + modifier: Modifier = Modifier, +) { + BitwardenActionCard( + cardTitle = stringResource(id = BitwardenString.upgraded_to_premium), + cardSubtitle = stringResource( + id = BitwardenString.you_now_have_access_to_all_advanced_security_features, + ), + actionText = stringResource(id = BitwardenString.learn_more), + leadingContent = { + Icon( + painter = rememberVectorPainter(id = BitwardenDrawable.ic_star), + contentDescription = null, + tint = BitwardenTheme.colorScheme.icon.secondary, + ) + }, + isExternalLink = true, + onActionClick = onActionClick, + onDismissClick = onDismissClick, + modifier = modifier, + ) +} diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/model/UpgradedToPremiumCardData.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/model/UpgradedToPremiumCardData.kt new file mode 100644 index 0000000000..39e583de4f --- /dev/null +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/model/UpgradedToPremiumCardData.kt @@ -0,0 +1,11 @@ +package com.x8bit.bitwarden.ui.tools.feature.send.model + +/** + * Handlers for the "Upgraded to Premium" action card shown on Send surfaces. + * + * Pass `null` to indicate the card should not be displayed. + */ +data class UpgradedToPremiumCardData( + val onCardClick: () -> Unit, + val onCardDismiss: () -> Unit, +) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendScreenTest.kt index f07cd1e739..23f75ebf34 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/SendScreenTest.kt @@ -1000,6 +1000,51 @@ class SendScreenTest : BitwardenComposeTest() { viewModel.trySendAction(SendAction.UpgradedToPremiumCardDismiss) } } + + @Test + fun `UpgradedToPremium action card should display in Empty viewState when eligible`() { + mutableStateFlow.update { + it.copy( + viewState = SendState.ViewState.Empty, + isUpgradedToPremiumCardEligible = true, + ) + } + + composeTestRule + .onNodeWithText(text = "Upgraded to Premium") + .assertIsDisplayed() + composeTestRule + .onNodeWithText(text = "Learn more") + .assertIsDisplayed() + } + + @Test + fun `UpgradedToPremium action card should not display in Loading viewState`() { + mutableStateFlow.update { + it.copy( + viewState = SendState.ViewState.Loading, + isUpgradedToPremiumCardEligible = true, + ) + } + + composeTestRule + .onNodeWithText(text = "Upgraded to Premium") + .assertDoesNotExist() + } + + @Test + fun `UpgradedToPremium action card should not display in Error viewState`() { + mutableStateFlow.update { + it.copy( + viewState = SendState.ViewState.Error("Fail".asText()), + isUpgradedToPremiumCardEligible = true, + ) + } + + composeTestRule + .onNodeWithText(text = "Upgraded to Premium") + .assertDoesNotExist() + } } private val DEFAULT_STATE: SendState = SendState(