Sends file tab is blocked by premium membership (#527)

This commit is contained in:
David Perez 2024-01-07 20:57:09 -06:00 committed by Álison Fernandes
parent 1cfd85d9f8
commit e57dc0393c
3 changed files with 93 additions and 5 deletions

View file

@ -4,6 +4,8 @@ import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
@ -12,6 +14,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.util.toSendView
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@ -27,6 +30,7 @@ private const val KEY_STATE = "state"
@HiltViewModel
class AddSendViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
authRepo: AuthRepository,
private val vaultRepo: VaultRepository,
) : BaseViewModel<AddSendState, AddSendEvent, AddSendAction>(
initialState = savedStateHandle[KEY_STATE] ?: AddSendState(
@ -45,6 +49,7 @@ class AddSendViewModel @Inject constructor(
),
),
dialogState = null,
isPremiumUser = authRepo.userStateFlow.value?.activeAccount?.isPremium == true,
),
) {
@ -52,6 +57,12 @@ class AddSendViewModel @Inject constructor(
stateFlow
.onEach { savedStateHandle[KEY_STATE] = it }
.launchIn(viewModelScope)
authRepo
.userStateFlow
.map { AddSendAction.Internal.UserStateReceive(it) }
.onEach(::sendAction)
.launchIn(viewModelScope)
}
override fun handleAction(action: AddSendAction): Unit = when (action) {
@ -74,6 +85,7 @@ class AddSendViewModel @Inject constructor(
private fun handleInternalAction(action: AddSendAction.Internal): Unit = when (action) {
is AddSendAction.Internal.CreateSendResultReceive -> handleCreateSendResultReceive(action)
is AddSendAction.Internal.UserStateReceive -> handleUserStateReceive(action)
}
private fun handleCreateSendResultReceive(
@ -98,6 +110,12 @@ class AddSendViewModel @Inject constructor(
}
}
private fun handleUserStateReceive(action: AddSendAction.Internal.UserStateReceive) {
mutableStateFlow.update {
it.copy(isPremiumUser = action.userState?.activeAccount?.isPremium == true)
}
}
private fun handlePasswordChange(action: AddSendAction.PasswordChange) {
updateCommonContent {
it.copy(passwordInput = action.input)
@ -164,6 +182,17 @@ class AddSendViewModel @Inject constructor(
}
private fun handleFileTypeClick() {
if (!state.isPremiumUser) {
mutableStateFlow.update {
it.copy(
dialogState = AddSendState.DialogState.Error(
title = R.string.send.asText(),
message = R.string.send_file_premium_required.asText(),
),
)
}
return
}
updateContent {
it.copy(selectedType = AddSendState.ViewState.Content.SendType.File)
}
@ -257,6 +286,7 @@ class AddSendViewModel @Inject constructor(
data class AddSendState(
val dialogState: DialogState?,
val viewState: ViewState,
val isPremiumUser: Boolean,
) : Parcelable {
/**
@ -438,6 +468,11 @@ sealed class AddSendAction {
* Models actions that the [AddSendViewModel] itself might send.
*/
sealed class Internal : AddSendAction() {
/**
* Indicates what the current [userState] is.
*/
data class UserStateReceive(val userState: UserState?) : Internal()
/**
* Indicates a result for creating a send has been received.
*/

View file

@ -601,6 +601,7 @@ class AddSendScreenTest : BaseComposeTest() {
private val DEFAULT_STATE = AddSendState(
viewState = DEFAULT_VIEW_STATE,
dialogState = null,
isPremiumUser = false,
)
}
}

View file

@ -4,6 +4,9 @@ import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.bitwarden.core.SendView
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
@ -15,6 +18,7 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
@ -23,6 +27,10 @@ import org.junit.jupiter.api.Test
class AddSendViewModelTest : BaseViewModelTest() {
private val mutableUserStateFlow = MutableStateFlow<UserState?>(DEFAULT_USER_STATE)
private val authRepository: AuthRepository = mockk {
every { userStateFlow } returns mutableUserStateFlow
}
private val vaultRepository: VaultRepository = mockk()
@BeforeEach
@ -43,7 +51,9 @@ class AddSendViewModelTest : BaseViewModelTest() {
@Test
fun `initial state should read from saved state when present`() {
val savedState = mockk<AddSendState>()
val savedState = DEFAULT_STATE.copy(
dialogState = AddSendState.DialogState.Loading("Loading".asText()),
)
val viewModel = createViewModel(savedState)
assertEquals(savedState, viewModel.stateFlow.value)
}
@ -158,21 +168,45 @@ class AddSendViewModelTest : BaseViewModelTest() {
}
@Test
fun `FileTypeClick and TextTypeClick should toggle sendType`() = runTest {
fun `FileTypeClick and TextTypeClick should toggle sendType when user is premium`() = runTest {
val viewModel = createViewModel()
val premiumUserState = DEFAULT_STATE.copy(isPremiumUser = true)
val expectedViewState = DEFAULT_VIEW_STATE.copy(
selectedType = AddSendState.ViewState.Content.SendType.File,
)
// Make sure we are a premium user
mutableUserStateFlow.tryEmit(
DEFAULT_USER_STATE.copy(
accounts = listOf(DEFAULT_USER_ACCOUNT_STATE.copy(isPremium = true)),
),
)
viewModel.stateFlow.test {
assertEquals(DEFAULT_STATE, awaitItem())
assertEquals(premiumUserState, awaitItem())
viewModel.trySendAction(AddSendAction.FileTypeClick)
assertEquals(DEFAULT_STATE.copy(viewState = expectedViewState), awaitItem())
assertEquals(premiumUserState.copy(viewState = expectedViewState), awaitItem())
viewModel.trySendAction(AddSendAction.TextTypeClick)
assertEquals(DEFAULT_STATE, awaitItem())
assertEquals(premiumUserState, awaitItem())
}
}
@Test
fun `FileTypeClick should display error dialog when account is not premium`() {
val viewModel = createViewModel()
viewModel.trySendAction(AddSendAction.FileTypeClick)
assertEquals(
DEFAULT_STATE.copy(
dialogState = AddSendState.DialogState.Error(
title = R.string.send.asText(),
message = R.string.send_file_premium_required.asText(),
),
),
viewModel.stateFlow.value,
)
}
@Test
fun `NameChange should update name input`() = runTest {
val viewModel = createViewModel()
@ -278,6 +312,7 @@ class AddSendViewModelTest : BaseViewModelTest() {
state: AddSendState? = null,
): AddSendViewModel = AddSendViewModel(
savedStateHandle = SavedStateHandle().apply { set("state", state) },
authRepo = authRepository,
vaultRepo = vaultRepository,
)
@ -307,6 +342,23 @@ class AddSendViewModelTest : BaseViewModelTest() {
private val DEFAULT_STATE = AddSendState(
viewState = DEFAULT_VIEW_STATE,
dialogState = null,
isPremiumUser = false,
)
private val DEFAULT_USER_ACCOUNT_STATE = UserState.Account(
userId = "user_id_1",
name = "Bit",
email = "bitwarden@gmail.com",
avatarColorHex = "#ff00ff",
environment = Environment.Us,
isPremium = false,
isVaultUnlocked = true,
organizations = emptyList(),
)
private val DEFAULT_USER_STATE = UserState(
activeUserId = "user_id_1",
accounts = listOf(DEFAULT_USER_ACCOUNT_STATE),
)
}
}