Subscribe to vault SendData (#485)

This commit is contained in:
David Perez 2024-01-03 14:39:41 -06:00 committed by Álison Fernandes
parent 11fcaa6678
commit c5989d117e
5 changed files with 242 additions and 19 deletions

View file

@ -1,11 +1,16 @@
package com.x8bit.bitwarden.ui.tools.feature.send
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
/**
@ -17,6 +22,19 @@ fun SendContent(
modifier: Modifier = Modifier,
) {
LazyColumn(modifier = modifier) {
item {
// TODO: Populate with real data BIT-481
Text(
text = "Not yet implemented",
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
)
}
item {
Spacer(modifier = Modifier.height(88.dp))
Spacer(modifier = Modifier.navigationBarsPadding())

View file

@ -3,15 +3,21 @@ package com.x8bit.bitwarden.ui.tools.feature.send
import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.base.util.concat
import com.x8bit.bitwarden.ui.tools.feature.send.util.toViewState
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemScreen
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import javax.inject.Inject
@ -33,11 +39,11 @@ class SendViewModel @Inject constructor(
) {
init {
// TODO: Remove this once we start listening to real vault data BIT-481
viewModelScope.launch {
delay(timeMillis = 3_000L)
mutableStateFlow.update { it.copy(viewState = SendState.ViewState.Empty) }
}
vaultRepo
.sendDataStateFlow
.map { SendAction.Internal.SendDataReceive(it) }
.onEach(::sendAction)
.launchIn(viewModelScope)
}
override fun handleAction(action: SendAction): Unit = when (action) {
@ -47,6 +53,55 @@ class SendViewModel @Inject constructor(
SendAction.RefreshClick -> handleRefreshClick()
SendAction.SearchClick -> handleSearchClick()
SendAction.SyncClick -> handleSyncClick()
is SendAction.Internal -> handleInternalAction(action)
}
private fun handleInternalAction(action: SendAction.Internal): Unit = when (action) {
is SendAction.Internal.SendDataReceive -> handleSendDataReceive(action)
}
private fun handleSendDataReceive(action: SendAction.Internal.SendDataReceive) {
when (val dataState = action.sendDataState) {
is DataState.Error -> {
mutableStateFlow.update {
it.copy(
viewState = SendState.ViewState.Error(
message = R.string.generic_error_message.asText(),
),
)
}
}
is DataState.Loaded -> {
mutableStateFlow.update {
it.copy(viewState = dataState.data.toViewState())
}
}
DataState.Loading -> {
mutableStateFlow.update {
it.copy(viewState = SendState.ViewState.Loading)
}
}
is DataState.NoNetwork -> {
mutableStateFlow.update {
it.copy(
viewState = SendState.ViewState.Error(
message = R.string.internet_connection_required_title
.asText()
.concat(R.string.internet_connection_required_message.asText()),
),
)
}
}
is DataState.Pending -> {
mutableStateFlow.update {
it.copy(viewState = dataState.data.toViewState())
}
}
}
}
private fun handleAboutSendClick() {
@ -164,6 +219,18 @@ sealed class SendAction {
* User clicked the sync button.
*/
data object SyncClick : SendAction()
/**
* Models actions that the [SendViewModel] itself will send.
*/
sealed class Internal : SendAction() {
/**
* Indicates that the send data has been received.
*/
data class SendDataReceive(
val sendDataState: DataState<SendData>,
) : Internal()
}
}
/**

View file

@ -0,0 +1,20 @@
package com.x8bit.bitwarden.ui.tools.feature.send.util
import com.bitwarden.core.SendView
import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.ui.tools.feature.send.SendState
/**
* Transforms [SendData] into [SendState.ViewState].
*/
fun SendData.toViewState(): SendState.ViewState =
this
.sendViewList
.takeUnless { it.isEmpty() }
?.toSendContent()
?: SendState.ViewState.Empty
private fun List<SendView>.toSendContent(): SendState.ViewState.Content {
// TODO: Populate with real data BIT-481
return SendState.ViewState.Content
}

View file

@ -2,21 +2,44 @@ package com.x8bit.bitwarden.ui.tools.feature.send
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.base.util.concat
import com.x8bit.bitwarden.ui.tools.feature.send.util.toViewState
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class SendViewModelTest : BaseViewModelTest() {
private val vaultRepo: VaultRepository = mockk()
private val mutableSendDataFlow = MutableStateFlow<DataState<SendData>>(DataState.Loading)
private val vaultRepo: VaultRepository = mockk {
every { sendDataStateFlow } returns mutableSendDataFlow
}
@BeforeEach
fun setup() {
mockkStatic(SEND_DATA_EXTENSIONS_PATH)
}
@AfterEach
fun tearDown() {
unmockkStatic(SEND_DATA_EXTENSIONS_PATH)
}
@Test
fun `initial state should be Empty`() {
@ -24,13 +47,6 @@ class SendViewModelTest : BaseViewModelTest() {
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
}
@Test
fun `initial state should read from saved state when present`() {
val savedState = mockk<SendState>()
val viewModel = createViewModel(state = savedState)
assertEquals(savedState, viewModel.stateFlow.value)
}
@Test
fun `AboutSendClick should emit NavigateToAboutSend`() = runTest {
val viewModel = createViewModel()
@ -94,6 +110,78 @@ class SendViewModelTest : BaseViewModelTest() {
}
}
@Test
fun `VaultRepository SendData Error should update view state to Error`() {
val viewModel = createViewModel()
mutableSendDataFlow.value = DataState.Error(Throwable("Fail"))
assertEquals(
SendState(
viewState = SendState.ViewState.Error(
message = R.string.generic_error_message.asText(),
),
),
viewModel.stateFlow.value,
)
}
@Test
fun `VaultRepository SendData Loaded should update view state`() {
val viewModel = createViewModel()
val viewState = SendState.ViewState.Content
val sendData = mockk<SendData> {
every { toViewState() } returns viewState
}
mutableSendDataFlow.value = DataState.Loaded(sendData)
assertEquals(SendState(viewState = viewState), viewModel.stateFlow.value)
}
@Test
fun `VaultRepository SendData Loading should update view state to Loading`() {
val viewModel = createViewModel()
mutableSendDataFlow.value = DataState.Loading
assertEquals(
SendState(viewState = SendState.ViewState.Loading),
viewModel.stateFlow.value,
)
}
@Test
fun `VaultRepository SendData NoNetwork should update view state to Error`() {
val viewModel = createViewModel()
mutableSendDataFlow.value = DataState.NoNetwork()
assertEquals(
SendState(
viewState = SendState.ViewState.Error(
message = R.string.internet_connection_required_title
.asText()
.concat(R.string.internet_connection_required_message.asText()),
),
),
viewModel.stateFlow.value,
)
}
@Test
fun `VaultRepository SendData Pending should update view state`() {
val viewModel = createViewModel()
val viewState = SendState.ViewState.Content
val sendData = mockk<SendData> {
every { toViewState() } returns viewState
}
mutableSendDataFlow.value = DataState.Pending(sendData)
assertEquals(SendState(viewState = viewState), viewModel.stateFlow.value)
}
private fun createViewModel(
state: SendState? = null,
vaultRepository: VaultRepository = vaultRepo,
@ -105,10 +193,9 @@ class SendViewModelTest : BaseViewModelTest() {
)
}
private const val SEND_DATA_EXTENSIONS_PATH: String =
"com.x8bit.bitwarden.ui.tools.feature.send.util.SendDataExtensionsKt"
private val DEFAULT_STATE: SendState = SendState(
viewState = SendState.ViewState.Loading,
)
private val DEFAULT_ERROR_STATE: SendState = DEFAULT_STATE.copy(
viewState = SendState.ViewState.Error("Fail".asText()),
)

View file

@ -0,0 +1,31 @@
package com.x8bit.bitwarden.ui.tools.feature.send.util
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.ui.tools.feature.send.SendState
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class SendDataExtensionsTest {
@Test
fun `toViewState should return Empty when SendData is empty`() {
val sendData = SendData(emptyList())
val result = sendData.toViewState()
assertEquals(SendState.ViewState.Empty, result)
}
@Test
fun `toViewState should return Content when SendData is not empty`() {
val list = listOf(
createMockSendView(number = 1),
)
val sendData = SendData(list)
val result = sendData.toViewState()
assertEquals(SendState.ViewState.Content, result)
}
}