mirror of
https://github.com/bitwarden/android.git
synced 2025-02-17 12:30:00 +03:00
BIT-482: Display share sheet after saving a new send (#532)
This commit is contained in:
parent
185849951f
commit
70a425dfd8
7 changed files with 104 additions and 29 deletions
|
@ -43,6 +43,7 @@ import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkSend
|
|||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipherList
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCollectionList
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolderList
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkSend
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkSendList
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toVaultUnlockResult
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
@ -435,12 +436,19 @@ class VaultRepositoryImpl(
|
|||
sendView = sendView,
|
||||
)
|
||||
.flatMap { send -> sendsService.createSend(body = send.toEncryptedNetworkSend()) }
|
||||
.onSuccess {
|
||||
// Save the send immediately, regardless of whether the decrypt succeeds
|
||||
vaultDiskSource.saveSend(userId = userId, send = it)
|
||||
}
|
||||
.flatMap {
|
||||
vaultSdkSource.decryptSend(
|
||||
userId = userId,
|
||||
send = it.toEncryptedSdkSend(),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onFailure = { CreateSendResult.Error },
|
||||
onSuccess = {
|
||||
vaultDiskSource.saveSend(userId = userId, send = it)
|
||||
CreateSendResult.Success
|
||||
},
|
||||
onSuccess = { CreateSendResult.Success(it) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository.model
|
||||
|
||||
import com.bitwarden.core.SendView
|
||||
|
||||
/**
|
||||
* Models result of creating a send.
|
||||
*/
|
||||
sealed class CreateSendResult {
|
||||
|
||||
/**
|
||||
* send created successfully.
|
||||
* Send created successfully and contains the decrypted [SendView].
|
||||
*/
|
||||
data object Success : CreateSendResult()
|
||||
data class Success(val sendView: SendView) : CreateSendResult()
|
||||
|
||||
/**
|
||||
* Generic error while creating a send.
|
||||
|
|
|
@ -19,6 +19,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler
|
||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenErrorContent
|
||||
|
@ -38,6 +39,7 @@ import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandler
|
|||
@Composable
|
||||
fun AddSendScreen(
|
||||
viewModel: AddSendViewModel = hiltViewModel(),
|
||||
intentHandler: IntentHandler = IntentHandler(LocalContext.current),
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
|
@ -47,6 +49,10 @@ fun AddSendScreen(
|
|||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
is AddSendEvent.NavigateBack -> onNavigateBack()
|
||||
is AddSendEvent.ShowShareSheet -> {
|
||||
intentHandler.shareText(event.message)
|
||||
}
|
||||
|
||||
is AddSendEvent.ShowToast -> {
|
||||
Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
|
|
@ -6,12 +6,15 @@ 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.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.baseWebSendUrl
|
||||
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
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.util.toSendView
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.util.toSendUrl
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
@ -31,6 +34,7 @@ private const val KEY_STATE = "state"
|
|||
class AddSendViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
authRepo: AuthRepository,
|
||||
private val environmentRepo: EnvironmentRepository,
|
||||
private val vaultRepo: VaultRepository,
|
||||
) : BaseViewModel<AddSendState, AddSendEvent, AddSendAction>(
|
||||
initialState = savedStateHandle[KEY_STATE] ?: AddSendState(
|
||||
|
@ -50,6 +54,7 @@ class AddSendViewModel @Inject constructor(
|
|||
),
|
||||
dialogState = null,
|
||||
isPremiumUser = authRepo.userStateFlow.value?.activeAccount?.isPremium == true,
|
||||
baseWebSendUrl = environmentRepo.environment.environmentUrlData.baseWebSendUrl,
|
||||
),
|
||||
) {
|
||||
|
||||
|
@ -91,7 +96,7 @@ class AddSendViewModel @Inject constructor(
|
|||
private fun handleCreateSendResultReceive(
|
||||
action: AddSendAction.Internal.CreateSendResultReceive,
|
||||
) {
|
||||
when (action.result) {
|
||||
when (val result = action.result) {
|
||||
CreateSendResult.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
|
@ -103,9 +108,14 @@ class AddSendViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
CreateSendResult.Success -> {
|
||||
is CreateSendResult.Success -> {
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
sendEvent(AddSendEvent.NavigateBack)
|
||||
sendEvent(
|
||||
AddSendEvent.ShowShareSheet(
|
||||
message = result.sendView.toSendUrl(state.baseWebSendUrl),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -287,6 +297,7 @@ data class AddSendState(
|
|||
val dialogState: DialogState?,
|
||||
val viewState: ViewState,
|
||||
val isPremiumUser: Boolean,
|
||||
val baseWebSendUrl: String,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
|
@ -383,6 +394,11 @@ sealed class AddSendEvent {
|
|||
*/
|
||||
data object NavigateBack : AddSendEvent()
|
||||
|
||||
/**
|
||||
* Show share sheet.
|
||||
*/
|
||||
data class ShowShareSheet(val message: String) : AddSendEvent()
|
||||
|
||||
/**
|
||||
* Show Toast.
|
||||
*/
|
||||
|
|
|
@ -1904,24 +1904,28 @@ class VaultRepositoryTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `createSend with sendsService createSend success should return CreateSendResult success`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
val userId = "mockId-1"
|
||||
val mockSendView = createMockSendView(number = 1)
|
||||
val mockSdkSend = createMockSdkSend(number = 1)
|
||||
val mockSend = createMockSend(number = 1)
|
||||
val mockSendViewResult = createMockSendView(number = 2)
|
||||
coEvery {
|
||||
vaultSdkSource.encryptSend(userId = userId, sendView = mockSendView)
|
||||
} returns createMockSdkSend(number = 1).asSuccess()
|
||||
val mockSend = createMockSend(number = 1)
|
||||
} returns mockSdkSend.asSuccess()
|
||||
coEvery {
|
||||
sendsService.createSend(body = createMockSendJsonRequest(number = 1))
|
||||
} returns mockSend.asSuccess()
|
||||
coEvery { vaultDiskSource.saveSend(userId, mockSend) } just runs
|
||||
coEvery {
|
||||
vaultSdkSource.decryptSend(userId, mockSdkSend)
|
||||
} returns mockSendViewResult.asSuccess()
|
||||
|
||||
val result = vaultRepository.createSend(sendView = mockSendView)
|
||||
|
||||
assertEquals(CreateSendResult.Success, result)
|
||||
assertEquals(CreateSendResult.Success(mockSendViewResult), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -17,10 +17,13 @@ import androidx.compose.ui.test.performScrollTo
|
|||
import androidx.compose.ui.test.performTextInput
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.util.isProgressBar
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
@ -31,6 +34,10 @@ import org.junit.Test
|
|||
class AddSendScreenTest : BaseComposeTest() {
|
||||
|
||||
private var onNavigateBackCalled = false
|
||||
|
||||
private val intentHandler: IntentHandler = mockk {
|
||||
every { shareText(any()) } just runs
|
||||
}
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<AddSendEvent>()
|
||||
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||
private val viewModel = mockk<AddSendViewModel>(relaxed = true) {
|
||||
|
@ -43,6 +50,7 @@ class AddSendScreenTest : BaseComposeTest() {
|
|||
composeTestRule.setContent {
|
||||
AddSendScreen(
|
||||
viewModel = viewModel,
|
||||
intentHandler = intentHandler,
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
)
|
||||
}
|
||||
|
@ -54,6 +62,15 @@ class AddSendScreenTest : BaseComposeTest() {
|
|||
assert(onNavigateBackCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ShowShareSheet should call shareText on IntentHandler`() {
|
||||
val text = "sharable stuff"
|
||||
mutableEventFlow.tryEmit(AddSendEvent.ShowShareSheet(text))
|
||||
verify {
|
||||
intentHandler.shareText(text)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on close icon click should send CloseClick`() {
|
||||
composeTestRule
|
||||
|
@ -602,6 +619,7 @@ class AddSendScreenTest : BaseComposeTest() {
|
|||
viewState = DEFAULT_VIEW_STATE,
|
||||
dialogState = null,
|
||||
isPremiumUser = false,
|
||||
baseWebSendUrl = "https://vault.bitwarden.com/#/send/",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,12 +6,14 @@ 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.EnvironmentRepository
|
||||
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
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.util.toSendView
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.util.toSendUrl
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
|
@ -31,16 +33,21 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
private val authRepository: AuthRepository = mockk {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
}
|
||||
private val environmentRepository: EnvironmentRepository = mockk {
|
||||
every { environment } returns Environment.Us
|
||||
}
|
||||
private val vaultRepository: VaultRepository = mockk()
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkStatic(ADD_SEND_STATE_EXTENSIONS_PATH)
|
||||
mockkStatic(SEND_VIEW_EXTENSIONS_PATH)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(ADD_SEND_STATE_EXTENSIONS_PATH)
|
||||
unmockkStatic(SEND_VIEW_EXTENSIONS_PATH)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -68,25 +75,33 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `SaveClick with createSend success should emit NavigateBack`() = runTest {
|
||||
val viewState = DEFAULT_VIEW_STATE.copy(
|
||||
common = DEFAULT_COMMON_STATE.copy(name = "input"),
|
||||
)
|
||||
val initialState = DEFAULT_STATE.copy(viewState = viewState)
|
||||
val mockSendView = mockk<SendView>()
|
||||
every { viewState.toSendView() } returns mockSendView
|
||||
coEvery { vaultRepository.createSend(mockSendView) } returns CreateSendResult.Success
|
||||
val viewModel = createViewModel(initialState)
|
||||
fun `SaveClick with createSend success should emit NavigateBack and ShowShareSheet`() =
|
||||
runTest {
|
||||
val viewState = DEFAULT_VIEW_STATE.copy(
|
||||
common = DEFAULT_COMMON_STATE.copy(name = "input"),
|
||||
)
|
||||
val initialState = DEFAULT_STATE.copy(viewState = viewState)
|
||||
val mockSendView = mockk<SendView>()
|
||||
every { viewState.toSendView() } returns mockSendView
|
||||
val sendUrl = "www.test.com/send/test"
|
||||
val resultSendView = mockk<SendView> {
|
||||
every { toSendUrl(DEFAULT_ENVIRONMENT_URL) } returns sendUrl
|
||||
}
|
||||
coEvery {
|
||||
vaultRepository.createSend(mockSendView)
|
||||
} returns CreateSendResult.Success(sendView = resultSendView)
|
||||
val viewModel = createViewModel(initialState)
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AddSendAction.SaveClick)
|
||||
assertEquals(AddSendEvent.NavigateBack, awaitItem())
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AddSendAction.SaveClick)
|
||||
assertEquals(AddSendEvent.NavigateBack, awaitItem())
|
||||
assertEquals(AddSendEvent.ShowShareSheet(sendUrl), awaitItem())
|
||||
}
|
||||
assertEquals(initialState, viewModel.stateFlow.value)
|
||||
coVerify(exactly = 1) {
|
||||
vaultRepository.createSend(mockSendView)
|
||||
}
|
||||
}
|
||||
assertEquals(initialState, viewModel.stateFlow.value)
|
||||
coVerify(exactly = 1) {
|
||||
vaultRepository.createSend(mockSendView)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SaveClick with createSend failure should show error dialog`() = runTest {
|
||||
|
@ -313,12 +328,15 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
): AddSendViewModel = AddSendViewModel(
|
||||
savedStateHandle = SavedStateHandle().apply { set("state", state) },
|
||||
authRepo = authRepository,
|
||||
environmentRepo = environmentRepository,
|
||||
vaultRepo = vaultRepository,
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val ADD_SEND_STATE_EXTENSIONS_PATH: String =
|
||||
"com.x8bit.bitwarden.ui.tools.feature.send.addsend.util.AddSendStateExtensionsKt"
|
||||
private const val SEND_VIEW_EXTENSIONS_PATH: String =
|
||||
"com.x8bit.bitwarden.ui.tools.feature.send.util.SendViewExtensionsKt"
|
||||
|
||||
private val DEFAULT_COMMON_STATE = AddSendState.ViewState.Content.Common(
|
||||
name = "",
|
||||
|
@ -339,10 +357,13 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
selectedType = DEFAULT_SELECTED_TYPE_STATE,
|
||||
)
|
||||
|
||||
private const val DEFAULT_ENVIRONMENT_URL = "https://vault.bitwarden.com/#/send/"
|
||||
|
||||
private val DEFAULT_STATE = AddSendState(
|
||||
viewState = DEFAULT_VIEW_STATE,
|
||||
dialogState = null,
|
||||
isPremiumUser = false,
|
||||
baseWebSendUrl = DEFAULT_ENVIRONMENT_URL,
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_ACCOUNT_STATE = UserState.Account(
|
||||
|
|
Loading…
Add table
Reference in a new issue