mirror of
https://github.com/bitwarden/android.git
synced 2024-11-27 03:49:36 +03:00
Modify Add Sends UI to allow for editing existing sends (#575)
This commit is contained in:
parent
7e0a14d3a0
commit
9e6c49fb7c
10 changed files with 677 additions and 55 deletions
|
@ -13,6 +13,8 @@ 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.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Icon
|
||||
|
@ -36,9 +38,11 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenPasswordField
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSegmentedButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenStepper
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.SegmentedButtonState
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers
|
||||
|
||||
/**
|
||||
|
@ -48,6 +52,7 @@ import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandler
|
|||
@Composable
|
||||
fun AddSendContent(
|
||||
state: AddSendState.ViewState.Content,
|
||||
isAddMode: Boolean,
|
||||
addSendHandlers: AddSendHandlers,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
@ -65,34 +70,36 @@ fun AddSendContent(
|
|||
onValueChange = addSendHandlers.onNamChange,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.type),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
if (isAddMode) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.type),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenSegmentedButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
options = listOf(
|
||||
SegmentedButtonState(
|
||||
text = stringResource(id = R.string.file),
|
||||
onClick = addSendHandlers.onFileTypeSelect,
|
||||
isChecked = state.selectedType is AddSendState.ViewState.Content.SendType.File,
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenSegmentedButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
options = listOf(
|
||||
SegmentedButtonState(
|
||||
text = stringResource(id = R.string.file),
|
||||
onClick = addSendHandlers.onFileTypeSelect,
|
||||
isChecked = state.isFileType,
|
||||
),
|
||||
SegmentedButtonState(
|
||||
text = stringResource(id = R.string.text),
|
||||
onClick = addSendHandlers.onTextTypeSelect,
|
||||
isChecked = state.isTextType,
|
||||
),
|
||||
),
|
||||
SegmentedButtonState(
|
||||
text = stringResource(id = R.string.text),
|
||||
onClick = addSendHandlers.onTextTypeSelect,
|
||||
isChecked = state.selectedType is AddSendState.ViewState.Content.SendType.Text,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
when (val type = state.selectedType) {
|
||||
is AddSendState.ViewState.Content.SendType.File -> {
|
||||
BitwardenListHeaderText(
|
||||
|
@ -161,6 +168,7 @@ fun AddSendContent(
|
|||
Spacer(modifier = Modifier.height(16.dp))
|
||||
AddSendOptions(
|
||||
state = state,
|
||||
isAddMode = isAddMode,
|
||||
addSendHandlers = addSendHandlers,
|
||||
)
|
||||
|
||||
|
@ -173,12 +181,15 @@ fun AddSendContent(
|
|||
* Displays a collapsable set of new send options.
|
||||
*
|
||||
* @param state The content state.
|
||||
* @param isAddMode When `true`, indicates that we are creating a new send and `false` when editing
|
||||
* an existing send.
|
||||
* @param addSendHandlers THe handlers various events.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun AddSendOptions(
|
||||
state: AddSendState.ViewState.Content,
|
||||
isAddMode: Boolean,
|
||||
addSendHandlers: AddSendHandlers,
|
||||
) {
|
||||
var isExpanded by rememberSaveable { mutableStateOf(false) }
|
||||
|
@ -221,25 +232,92 @@ private fun AddSendOptions(
|
|||
modifier = Modifier.clipToBounds(),
|
||||
) {
|
||||
Column {
|
||||
SendDeletionDateChooser(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
dateFormatPattern = state.common.dateFormatPattern,
|
||||
timeFormatPattern = state.common.timeFormatPattern,
|
||||
currentZonedDateTime = state.common.deletionDate,
|
||||
onDateSelect = addSendHandlers.onDeletionDateChange,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
SendExpirationDateChooser(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
dateFormatPattern = state.common.dateFormatPattern,
|
||||
timeFormatPattern = state.common.timeFormatPattern,
|
||||
currentZonedDateTime = state.common.expirationDate,
|
||||
onDateSelect = addSendHandlers.onExpirationDateChange,
|
||||
)
|
||||
if (isAddMode) {
|
||||
SendDeletionDateChooser(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
dateFormatPattern = state.common.dateFormatPattern,
|
||||
timeFormatPattern = state.common.timeFormatPattern,
|
||||
currentZonedDateTime = state.common.deletionDate,
|
||||
onDateSelect = addSendHandlers.onDeletionDateChange,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
SendExpirationDateChooser(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
dateFormatPattern = state.common.dateFormatPattern,
|
||||
timeFormatPattern = state.common.timeFormatPattern,
|
||||
currentZonedDateTime = state.common.expirationDate,
|
||||
onDateSelect = addSendHandlers.onExpirationDateChange,
|
||||
)
|
||||
} else {
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.deletion_date),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
AddSendCustomDateChooser(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
dateFormatPattern = state.common.dateFormatPattern,
|
||||
timeFormatPattern = state.common.timeFormatPattern,
|
||||
currentZonedDateTime = state.common.deletionDate,
|
||||
onDateSelect = { addSendHandlers.onDeletionDateChange(requireNotNull(it)) },
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.deletion_date_info),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.expiration_date),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
AddSendCustomDateChooser(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
dateFormatPattern = state.common.dateFormatPattern,
|
||||
timeFormatPattern = state.common.timeFormatPattern,
|
||||
currentZonedDateTime = state.common.expirationDate,
|
||||
onDateSelect = addSendHandlers.onExpirationDateChange,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.expiration_date_info),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
BitwardenTextButton(
|
||||
label = stringResource(id = R.string.clear),
|
||||
onClick = addSendHandlers.onClearExpirationDateClick,
|
||||
isEnabled = state.common.expirationDate != null,
|
||||
modifier = Modifier.wrapContentWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenStepper(
|
||||
label = stringResource(id = R.string.maximum_access_count),
|
||||
|
@ -260,6 +338,30 @@ private fun AddSendOptions(
|
|||
.fillMaxWidth()
|
||||
.padding(horizontal = 32.dp),
|
||||
)
|
||||
if (!isAddMode) {
|
||||
state.common.currentAccessCount?.let {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 32.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.current_access_count) + ":",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = it.toString(),
|
||||
style = LocalNonMaterialTypography.current.bodySmallProminent,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenPasswordField(
|
||||
label = stringResource(id = R.string.new_password),
|
||||
|
|
|
@ -135,6 +135,7 @@ fun AddSendScreen(
|
|||
when (val viewState = state.viewState) {
|
||||
is AddSendState.ViewState.Content -> AddSendContent(
|
||||
state = viewState,
|
||||
isAddMode = state.isAddMode,
|
||||
addSendHandlers = remember(viewModel) { AddSendHandlers.create(viewModel) },
|
||||
modifier = modifier,
|
||||
)
|
||||
|
|
|
@ -3,18 +3,24 @@ package com.x8bit.bitwarden.ui.tools.feature.send.addsend
|
|||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.DataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.baseWebSendUrl
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.takeUntilLoaded
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
|
||||
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.addsend.model.AddSendType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.util.toSendView
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.util.toSendUrl
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
@ -50,6 +56,7 @@ class AddSendViewModel @Inject constructor(
|
|||
AddSendType.AddItem -> AddSendState.ViewState.Content(
|
||||
common = AddSendState.ViewState.Content.Common(
|
||||
name = "",
|
||||
currentAccessCount = null,
|
||||
maxAccessCount = null,
|
||||
passwordInput = "",
|
||||
noteInput = "",
|
||||
|
@ -62,6 +69,7 @@ class AddSendViewModel @Inject constructor(
|
|||
.truncatedTo(ChronoUnit.DAYS)
|
||||
.plusWeeks(1),
|
||||
expirationDate = null,
|
||||
sendUrl = null,
|
||||
),
|
||||
selectedType = AddSendState.ViewState.Content.SendType.Text(
|
||||
input = "",
|
||||
|
@ -69,9 +77,7 @@ class AddSendViewModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
|
||||
is AddSendType.EditItem -> AddSendState.ViewState.Error(
|
||||
"Not yet implemented".asText(),
|
||||
)
|
||||
is AddSendType.EditItem -> AddSendState.ViewState.Loading
|
||||
},
|
||||
dialogState = null,
|
||||
isPremiumUser = authRepo.userStateFlow.value?.activeAccount?.isPremium == true,
|
||||
|
@ -85,6 +91,19 @@ class AddSendViewModel @Inject constructor(
|
|||
.onEach { savedStateHandle[KEY_STATE] = it }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
when (val addSendType = state.addSendType) {
|
||||
AddSendType.AddItem -> Unit
|
||||
is AddSendType.EditItem -> {
|
||||
vaultRepo
|
||||
.getSendStateFlow(addSendType.sendItemId)
|
||||
// We'll stop getting updates as soon as we get some loaded data.
|
||||
.takeUntilLoaded()
|
||||
.map { AddSendAction.Internal.SendDataReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
}
|
||||
|
||||
authRepo
|
||||
.userStateFlow
|
||||
.map { AddSendAction.Internal.UserStateReceive(it) }
|
||||
|
@ -100,6 +119,7 @@ class AddSendViewModel @Inject constructor(
|
|||
is AddSendAction.CloseClick -> handleCloseClick()
|
||||
is AddSendAction.DeletionDateChange -> handleDeletionDateChange(action)
|
||||
is AddSendAction.ExpirationDateChange -> handleExpirationDateChange(action)
|
||||
AddSendAction.ClearExpirationDate -> handleClearExpirationDate()
|
||||
AddSendAction.DismissDialogClick -> handleDismissDialogClick()
|
||||
is AddSendAction.SaveClick -> handleSaveClick()
|
||||
is AddSendAction.FileTypeClick -> handleFileTypeClick()
|
||||
|
@ -118,7 +138,9 @@ class AddSendViewModel @Inject constructor(
|
|||
|
||||
private fun handleInternalAction(action: AddSendAction.Internal): Unit = when (action) {
|
||||
is AddSendAction.Internal.CreateSendResultReceive -> handleCreateSendResultReceive(action)
|
||||
is AddSendAction.Internal.UpdateSendResultReceive -> handleUpdateSendResultReceive(action)
|
||||
is AddSendAction.Internal.UserStateReceive -> handleUserStateReceive(action)
|
||||
is AddSendAction.Internal.SendDataReceive -> handleSendDataReceive(action)
|
||||
}
|
||||
|
||||
private fun handleCreateSendResultReceive(
|
||||
|
@ -148,12 +170,113 @@ class AddSendViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleUpdateSendResultReceive(
|
||||
action: AddSendAction.Internal.UpdateSendResultReceive,
|
||||
) {
|
||||
when (val result = action.result) {
|
||||
is UpdateSendResult.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = AddSendState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = result
|
||||
.errorMessage
|
||||
?.asText()
|
||||
?: R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is UpdateSendResult.Success -> {
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
sendEvent(AddSendEvent.NavigateBack)
|
||||
sendEvent(
|
||||
AddSendEvent.ShowShareSheet(
|
||||
message = result.sendView.toSendUrl(state.baseWebSendUrl),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUserStateReceive(action: AddSendAction.Internal.UserStateReceive) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(isPremiumUser = action.userState?.activeAccount?.isPremium == true)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun handleSendDataReceive(action: AddSendAction.Internal.SendDataReceive) {
|
||||
when (val sendDataState = action.sendDataState) {
|
||||
is DataState.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = AddSendState.ViewState.Error(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is DataState.Loaded -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = sendDataState
|
||||
.data
|
||||
?.toViewState(
|
||||
clock = clock,
|
||||
baseWebSendUrl = environmentRepo
|
||||
.environment
|
||||
.environmentUrlData
|
||||
.baseWebSendUrl,
|
||||
)
|
||||
?: AddSendState.ViewState.Error(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DataState.Loading -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(viewState = AddSendState.ViewState.Loading)
|
||||
}
|
||||
}
|
||||
|
||||
is DataState.NoNetwork -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = AddSendState.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 = sendDataState
|
||||
.data
|
||||
?.toViewState(
|
||||
clock = clock,
|
||||
baseWebSendUrl = environmentRepo
|
||||
.environment
|
||||
.environmentUrlData
|
||||
.baseWebSendUrl,
|
||||
)
|
||||
?: AddSendState.ViewState.Error(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCopyLinkClick() {
|
||||
// TODO Add copy link support (BIT-1435)
|
||||
sendEvent(AddSendEvent.ShowToast("Not yet implemented"))
|
||||
|
@ -212,6 +335,10 @@ class AddSendViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleClearExpirationDate() {
|
||||
updateCommonContent { it.copy(expirationDate = null) }
|
||||
}
|
||||
|
||||
private fun handleSaveClick() {
|
||||
onContent { content ->
|
||||
if (content.common.name.isBlank()) {
|
||||
|
@ -235,8 +362,20 @@ class AddSendViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
viewModelScope.launch {
|
||||
val result = vaultRepo.createSend(content.toSendView(clock))
|
||||
sendAction(AddSendAction.Internal.CreateSendResultReceive(result))
|
||||
when (val addSendType = state.addSendType) {
|
||||
AddSendType.AddItem -> {
|
||||
val result = vaultRepo.createSend(content.toSendView(clock))
|
||||
sendAction(AddSendAction.Internal.CreateSendResultReceive(result))
|
||||
}
|
||||
|
||||
is AddSendType.EditItem -> {
|
||||
val result = vaultRepo.updateSend(
|
||||
sendId = addSendType.sendItemId,
|
||||
sendView = content.toSendView(clock),
|
||||
)
|
||||
sendAction(AddSendAction.Internal.UpdateSendResultReceive(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -402,12 +541,23 @@ data class AddSendState(
|
|||
val selectedType: SendType,
|
||||
) : ViewState() {
|
||||
|
||||
/**
|
||||
* Helper method to indicate if the selected type is [SendType.File].
|
||||
*/
|
||||
val isFileType: Boolean get() = selectedType is SendType.File
|
||||
|
||||
/**
|
||||
* Helper method to indicate if the selected type is [SendType.Text].
|
||||
*/
|
||||
val isTextType: Boolean get() = selectedType is SendType.Text
|
||||
|
||||
/**
|
||||
* Content data that is common for all item types.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Common(
|
||||
val name: String,
|
||||
val currentAccessCount: Int?,
|
||||
val maxAccessCount: Int?,
|
||||
val passwordInput: String,
|
||||
val noteInput: String,
|
||||
|
@ -415,6 +565,7 @@ data class AddSendState(
|
|||
val isDeactivateChecked: Boolean,
|
||||
val deletionDate: ZonedDateTime,
|
||||
val expirationDate: ZonedDateTime?,
|
||||
val sendUrl: String?,
|
||||
) : Parcelable {
|
||||
val dateFormatPattern: String get() = "M/d/yyyy"
|
||||
|
||||
|
@ -592,6 +743,11 @@ sealed class AddSendAction {
|
|||
*/
|
||||
data class ExpirationDateChange(val expirationDate: ZonedDateTime?) : AddSendAction()
|
||||
|
||||
/**
|
||||
* The user has cleared the expiration date.
|
||||
*/
|
||||
data object ClearExpirationDate : AddSendAction()
|
||||
|
||||
/**
|
||||
* Models actions that the [AddSendViewModel] itself might send.
|
||||
*/
|
||||
|
@ -605,5 +761,15 @@ sealed class AddSendAction {
|
|||
* Indicates a result for creating a send has been received.
|
||||
*/
|
||||
data class CreateSendResultReceive(val result: CreateSendResult) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates a result for updating a send has been received.
|
||||
*/
|
||||
data class UpdateSendResultReceive(val result: UpdateSendResult) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the send item data has been received.
|
||||
*/
|
||||
data class SendDataReceive(val sendDataState: DataState<SendView?>) : Internal()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ data class AddSendHandlers(
|
|||
val onDeactivateSendToggle: (Boolean) -> Unit,
|
||||
val onDeletionDateChange: (ZonedDateTime) -> Unit,
|
||||
val onExpirationDateChange: (ZonedDateTime?) -> Unit,
|
||||
val onClearExpirationDateClick: () -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
|
@ -57,6 +58,9 @@ data class AddSendHandlers(
|
|||
onExpirationDateChange = {
|
||||
viewModel.trySendAction(AddSendAction.ExpirationDateChange(it))
|
||||
},
|
||||
onClearExpirationDateClick = {
|
||||
viewModel.trySendAction(AddSendAction.ClearExpirationDate)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package com.x8bit.bitwarden.ui.tools.feature.send.addsend.util
|
||||
|
||||
import com.bitwarden.core.SendType
|
||||
import com.bitwarden.core.SendView
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.AddSendState
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.util.toSendUrl
|
||||
import java.time.Clock
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Transforms [SendView] into [AddSendState.ViewState.Content].
|
||||
*/
|
||||
fun SendView.toViewState(
|
||||
clock: Clock,
|
||||
baseWebSendUrl: String,
|
||||
): AddSendState.ViewState.Content =
|
||||
AddSendState.ViewState.Content(
|
||||
common = AddSendState.ViewState.Content.Common(
|
||||
name = this.name,
|
||||
currentAccessCount = this.accessCount.toInt(),
|
||||
maxAccessCount = this.maxAccessCount?.toInt(),
|
||||
// We do not set the password here
|
||||
// We only allow them to create new passwords, not view old ones
|
||||
passwordInput = "",
|
||||
noteInput = this.notes.orEmpty(),
|
||||
isHideEmailChecked = this.hideEmail,
|
||||
isDeactivateChecked = this.disabled,
|
||||
deletionDate = ZonedDateTime.ofInstant(this.deletionDate, clock.zone),
|
||||
expirationDate = this.expirationDate?.let { ZonedDateTime.ofInstant(it, clock.zone) },
|
||||
sendUrl = this.toSendUrl(baseWebSendUrl),
|
||||
),
|
||||
selectedType = when (type) {
|
||||
SendType.TEXT -> {
|
||||
AddSendState.ViewState.Content.SendType.Text(
|
||||
input = this.text?.text.orEmpty(),
|
||||
isHideByDefaultChecked = this.text?.hidden == true,
|
||||
)
|
||||
}
|
||||
|
||||
SendType.FILE -> AddSendState.ViewState.Content.SendType.File
|
||||
},
|
||||
)
|
|
@ -2,6 +2,9 @@ package com.x8bit.bitwarden.ui.tools.feature.send.addsend
|
|||
|
||||
import androidx.compose.ui.test.assert
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsEnabled
|
||||
import androidx.compose.ui.test.assertIsNotDisplayed
|
||||
import androidx.compose.ui.test.assertIsNotEnabled
|
||||
import androidx.compose.ui.test.assertIsOff
|
||||
import androidx.compose.ui.test.assertIsOn
|
||||
import androidx.compose.ui.test.assertTextEquals
|
||||
|
@ -22,6 +25,7 @@ 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.tools.feature.send.addsend.model.AddSendType
|
||||
import com.x8bit.bitwarden.ui.util.isEditableText
|
||||
import com.x8bit.bitwarden.ui.util.isProgressBar
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
|
@ -35,6 +39,7 @@ import org.junit.Before
|
|||
import org.junit.Test
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class AddSendScreenTest : BaseComposeTest() {
|
||||
|
||||
private var onNavigateBackCalled = false
|
||||
|
@ -229,6 +234,41 @@ class AddSendScreenTest : BaseComposeTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `segmented buttons should appear based on state`() {
|
||||
mutableStateFlow.update { it.copy(addSendType = AddSendType.AddItem) }
|
||||
composeTestRule
|
||||
.onNodeWithText("Type")
|
||||
.performScrollTo()
|
||||
.assertIsDisplayed()
|
||||
composeTestRule
|
||||
.onAllNodesWithText("File")
|
||||
.filterToOne(!isEditableText)
|
||||
.performScrollTo()
|
||||
.assertIsDisplayed()
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Text")
|
||||
.filterToOne(!isEditableText)
|
||||
.performScrollTo()
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(addSendType = AddSendType.EditItem(sendItemId = "sendId"))
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Type")
|
||||
.assertIsNotDisplayed()
|
||||
composeTestRule
|
||||
.onAllNodesWithText("File")
|
||||
.filterToOne(!isEditableText)
|
||||
.assertIsNotDisplayed()
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Text")
|
||||
.filterToOne(!isEditableText)
|
||||
.assertIsNotDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `File segmented button click should send FileTypeClick`() {
|
||||
composeTestRule
|
||||
|
@ -594,6 +634,64 @@ class AddSendScreenTest : BaseComposeTest() {
|
|||
.assertIsOn()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in edit mode, clear button should be enabled based on state`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(addSendType = AddSendType.EditItem(sendItemId = "sendId"))
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Options")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
composeTestRule
|
||||
.onNodeWithText("Clear")
|
||||
.performScrollTo()
|
||||
.assertIsNotEnabled()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = DEFAULT_VIEW_STATE.copy(
|
||||
common = DEFAULT_COMMON_STATE.copy(
|
||||
expirationDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Clear")
|
||||
.performScrollTo()
|
||||
.assertIsEnabled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in edit mode, clear button should send ClearExpirationDate`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
addSendType = AddSendType.EditItem(sendItemId = "sendId"),
|
||||
viewState = DEFAULT_VIEW_STATE.copy(
|
||||
common = DEFAULT_COMMON_STATE.copy(
|
||||
expirationDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Options")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
composeTestRule
|
||||
.onNodeWithText("Clear")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(AddSendAction.ClearExpirationDate)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `progressbar should be displayed according to state`() {
|
||||
mutableStateFlow.update {
|
||||
|
@ -683,6 +781,7 @@ class AddSendScreenTest : BaseComposeTest() {
|
|||
companion object {
|
||||
private val DEFAULT_COMMON_STATE = AddSendState.ViewState.Content.Common(
|
||||
name = "",
|
||||
currentAccessCount = null,
|
||||
maxAccessCount = null,
|
||||
passwordInput = "",
|
||||
noteInput = "",
|
||||
|
@ -690,6 +789,7 @@ class AddSendScreenTest : BaseComposeTest() {
|
|||
isDeactivateChecked = false,
|
||||
deletionDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
expirationDate = null,
|
||||
sendUrl = null,
|
||||
)
|
||||
|
||||
private val DEFAULT_SELECTED_TYPE_STATE = AddSendState.ViewState.Content.SendType.Text(
|
||||
|
|
|
@ -7,13 +7,17 @@ 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.DataState
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
|
||||
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.model.AddSendType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.util.toSendView
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.util.toSendUrl
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
|
@ -45,18 +49,27 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
private val environmentRepository: EnvironmentRepository = mockk {
|
||||
every { environment } returns Environment.Us
|
||||
}
|
||||
private val vaultRepository: VaultRepository = mockk()
|
||||
private val mutableSendDataStateFlow = MutableStateFlow<DataState<SendView>>(DataState.Loading)
|
||||
private val vaultRepository: VaultRepository = mockk {
|
||||
every { getSendStateFlow(any()) } returns mutableSendDataStateFlow
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkStatic(ADD_SEND_STATE_EXTENSIONS_PATH)
|
||||
mockkStatic(SEND_VIEW_EXTENSIONS_PATH)
|
||||
mockkStatic(
|
||||
ADD_SEND_STATE_EXTENSIONS_PATH,
|
||||
ADD_SEND_VIEW_EXTENSIONS_PATH,
|
||||
SEND_VIEW_EXTENSIONS_PATH,
|
||||
)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(ADD_SEND_STATE_EXTENSIONS_PATH)
|
||||
unmockkStatic(SEND_VIEW_EXTENSIONS_PATH)
|
||||
unmockkStatic(
|
||||
ADD_SEND_STATE_EXTENSIONS_PATH,
|
||||
ADD_SEND_VIEW_EXTENSIONS_PATH,
|
||||
SEND_VIEW_EXTENSIONS_PATH,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -149,6 +162,90 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SaveClick with updateSend success should emit NavigateBack and ShowShareSheet`() =
|
||||
runTest {
|
||||
val sendId = "sendId-1"
|
||||
val viewState = DEFAULT_VIEW_STATE.copy(
|
||||
common = DEFAULT_COMMON_STATE.copy(name = "input"),
|
||||
)
|
||||
val initialState = DEFAULT_STATE.copy(
|
||||
addSendType = AddSendType.EditItem(sendId),
|
||||
viewState = viewState,
|
||||
)
|
||||
val mockSendView = createMockSendView(number = 1)
|
||||
every { mockSendView.toViewState(clock, DEFAULT_ENVIRONMENT_URL) } returns viewState
|
||||
every { viewState.toSendView(clock) } returns mockSendView
|
||||
val sendUrl = "www.test.com/send/test"
|
||||
val resultSendView = mockk<SendView> {
|
||||
every { toSendUrl(DEFAULT_ENVIRONMENT_URL) } returns sendUrl
|
||||
every { id } returns sendId
|
||||
}
|
||||
coEvery {
|
||||
vaultRepository.updateSend(sendId = sendId, sendView = mockSendView)
|
||||
} returns UpdateSendResult.Success(sendView = resultSendView)
|
||||
mutableSendDataStateFlow.value = DataState.Loaded(mockSendView)
|
||||
val viewModel = createViewModel(initialState, AddSendType.EditItem(sendId))
|
||||
|
||||
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.updateSend(sendId = sendId, sendView = mockSendView)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SaveClick with updateSend failure should show error dialog`() = runTest {
|
||||
val sendId = "sendId-1"
|
||||
val viewState = DEFAULT_VIEW_STATE.copy(
|
||||
common = DEFAULT_COMMON_STATE.copy(name = "input"),
|
||||
)
|
||||
val initialState = DEFAULT_STATE.copy(
|
||||
addSendType = AddSendType.EditItem(sendId),
|
||||
viewState = viewState,
|
||||
)
|
||||
val mockSendView = mockk<SendView> {
|
||||
every { id } returns sendId
|
||||
}
|
||||
val errorMessage = "Failure"
|
||||
every { mockSendView.toViewState(clock, DEFAULT_ENVIRONMENT_URL) } returns viewState
|
||||
every { viewState.toSendView(clock) } returns mockSendView
|
||||
coEvery {
|
||||
vaultRepository.updateSend(sendId = sendId, sendView = mockSendView)
|
||||
} returns UpdateSendResult.Error(errorMessage = errorMessage)
|
||||
mutableSendDataStateFlow.value = DataState.Loaded(mockSendView)
|
||||
val viewModel = createViewModel(initialState, AddSendType.EditItem(sendId))
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(initialState, awaitItem())
|
||||
viewModel.trySendAction(AddSendAction.SaveClick)
|
||||
assertEquals(
|
||||
initialState.copy(
|
||||
dialogState = AddSendState.DialogState.Loading(
|
||||
message = R.string.saving.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
initialState.copy(
|
||||
dialogState = AddSendState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = errorMessage.asText(),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
coVerify(exactly = 1) {
|
||||
vaultRepository.updateSend(sendId = sendId, sendView = mockSendView)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SaveClick with blank name should show error dialog`() {
|
||||
val viewModel = createViewModel(DEFAULT_STATE)
|
||||
|
@ -276,6 +373,26 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ClearExpirationDate should clear the expiration date`() {
|
||||
val initialState = DEFAULT_STATE.copy(
|
||||
viewState = DEFAULT_VIEW_STATE.copy(
|
||||
DEFAULT_COMMON_STATE.copy(
|
||||
expirationDate = ZonedDateTime.parse("2024-09-13T00:00Z"),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel(initialState)
|
||||
|
||||
viewModel.trySendAction(AddSendAction.ClearExpirationDate)
|
||||
|
||||
assertEquals(
|
||||
// DEFAULT expiration date is null
|
||||
DEFAULT_STATE,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ChooseFileClick should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
@ -450,7 +567,7 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
addSendType: AddSendType = AddSendType.AddItem,
|
||||
): AddSendViewModel = AddSendViewModel(
|
||||
savedStateHandle = SavedStateHandle().apply {
|
||||
set("state", state)
|
||||
set("state", state?.copy(addSendType = addSendType))
|
||||
set(
|
||||
"add_send_item_type",
|
||||
when (addSendType) {
|
||||
|
@ -469,11 +586,14 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
companion object {
|
||||
private const val ADD_SEND_STATE_EXTENSIONS_PATH: String =
|
||||
"com.x8bit.bitwarden.ui.tools.feature.send.addsend.util.AddSendStateExtensionsKt"
|
||||
private const val ADD_SEND_VIEW_EXTENSIONS_PATH: String =
|
||||
"com.x8bit.bitwarden.ui.tools.feature.send.addsend.util.SendViewExtensionsKt"
|
||||
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 = "",
|
||||
currentAccessCount = null,
|
||||
maxAccessCount = null,
|
||||
passwordInput = "",
|
||||
noteInput = "",
|
||||
|
@ -481,6 +601,7 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
isDeactivateChecked = false,
|
||||
deletionDate = ZonedDateTime.parse("2023-11-03T00:00Z"),
|
||||
expirationDate = null,
|
||||
sendUrl = null,
|
||||
)
|
||||
|
||||
private val DEFAULT_SELECTED_TYPE_STATE = AddSendState.ViewState.Content.SendType.Text(
|
||||
|
|
|
@ -61,6 +61,7 @@ private val FIXED_CLOCK: Clock = Clock.fixed(
|
|||
|
||||
private val DEFAULT_COMMON_STATE = AddSendState.ViewState.Content.Common(
|
||||
name = "mockName-1",
|
||||
currentAccessCount = 1,
|
||||
maxAccessCount = 1,
|
||||
passwordInput = "mockPassword-1",
|
||||
noteInput = "mockNotes-1",
|
||||
|
@ -68,6 +69,7 @@ private val DEFAULT_COMMON_STATE = AddSendState.ViewState.Content.Common(
|
|||
isDeactivateChecked = false,
|
||||
deletionDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
expirationDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||
sendUrl = null,
|
||||
)
|
||||
|
||||
private val DEFAULT_SELECTED_TYPE_STATE = AddSendState.ViewState.Content.SendType.Text(
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
package com.x8bit.bitwarden.ui.tools.feature.send.addsend.util
|
||||
|
||||
import com.bitwarden.core.SendType
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.AddSendState
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
class SendViewExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `toViewState should create an appropriate ViewState for file type`() {
|
||||
val sendView = createMockSendView(number = 1, type = SendType.FILE)
|
||||
|
||||
val result = sendView.toViewState(
|
||||
clock = FIXED_CLOCK,
|
||||
baseWebSendUrl = "www.test.com/",
|
||||
)
|
||||
|
||||
assertEquals(DEFAULT_STATE, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toViewState should create an appropriate ViewState for text type`() {
|
||||
val sendView = createMockSendView(number = 1, type = SendType.TEXT)
|
||||
|
||||
val result = sendView.toViewState(
|
||||
clock = FIXED_CLOCK,
|
||||
baseWebSendUrl = "www.test.com/",
|
||||
)
|
||||
|
||||
assertEquals(DEFAULT_STATE.copy(selectedType = DEFAULT_TEXT_TYPE), result)
|
||||
}
|
||||
}
|
||||
|
||||
private val FIXED_CLOCK: Clock = Clock.fixed(
|
||||
Instant.parse("2023-10-27T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
private val DEFAULT_COMMON: AddSendState.ViewState.Content.Common =
|
||||
AddSendState.ViewState.Content.Common(
|
||||
name = "mockName-1",
|
||||
currentAccessCount = 1,
|
||||
maxAccessCount = 1,
|
||||
passwordInput = "",
|
||||
noteInput = "mockNotes-1",
|
||||
isHideEmailChecked = false,
|
||||
isDeactivateChecked = false,
|
||||
deletionDate = ZonedDateTime.ofInstant(
|
||||
Instant.parse("2023-10-27T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
),
|
||||
expirationDate = ZonedDateTime.ofInstant(
|
||||
Instant.parse("2023-10-27T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
),
|
||||
sendUrl = "www.test.com/mockAccessId-1/mockKey-1",
|
||||
)
|
||||
|
||||
private val DEFAULT_TEXT_TYPE: AddSendState.ViewState.Content.SendType.Text =
|
||||
AddSendState.ViewState.Content.SendType.Text(
|
||||
input = "mockText-1",
|
||||
isHideByDefaultChecked = false,
|
||||
)
|
||||
|
||||
private val DEFAULT_FILE_TYPE: AddSendState.ViewState.Content.SendType.File =
|
||||
AddSendState.ViewState.Content.SendType.File
|
||||
|
||||
private val DEFAULT_STATE: AddSendState.ViewState.Content =
|
||||
AddSendState.ViewState.Content(
|
||||
common = DEFAULT_COMMON,
|
||||
selectedType = DEFAULT_FILE_TYPE,
|
||||
)
|
|
@ -18,6 +18,12 @@ import androidx.compose.ui.test.onNodeWithText
|
|||
import androidx.compose.ui.test.performScrollToNode
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
|
||||
/**
|
||||
* A [SemanticsMatcher] used to find editable text nodes.
|
||||
*/
|
||||
val isEditableText: SemanticsMatcher
|
||||
get() = SemanticsMatcher.keyIsDefined(SemanticsProperties.EditableText)
|
||||
|
||||
/**
|
||||
* A [SemanticsMatcher] used to find progressbar nodes.
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue