From 8c2e2f8af6ec10017c5b30b93aaeee7dcc52e492 Mon Sep 17 00:00:00 2001 From: David Perez Date: Tue, 9 Jan 2024 11:46:15 -0600 Subject: [PATCH] BIT-484: Add deletion date and time pickers (#548) --- .../dialog/BitwardenDateSelectButton.kt | 15 +++- .../feature/send/SendDeletionDateChooser.kt | 67 ++++++++++++++++- .../feature/send/addsend/AddSendContent.kt | 7 ++ .../feature/send/addsend/AddSendViewModel.kt | 38 +++++++--- .../send/addsend/handlers/AddSendHandlers.kt | 5 ++ .../addsend/util/AddSendStateExtensions.kt | 12 ++-- .../feature/send/addsend/AddSendScreenTest.kt | 4 +- .../send/addsend/AddSendViewModelTest.kt | 32 +++++++-- .../util/AddSendStateExtensionsTest.kt | 72 ++++++++----------- 9 files changed, 183 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/dialog/BitwardenDateSelectButton.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/dialog/BitwardenDateSelectButton.kt index 42e350e4e..a4da8010e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/dialog/BitwardenDateSelectButton.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/dialog/BitwardenDateSelectButton.kt @@ -27,6 +27,8 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.role import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.util.toFormattedPattern +import java.time.Instant +import java.time.ZoneOffset import java.time.ZonedDateTime /** @@ -46,7 +48,7 @@ import java.time.ZonedDateTime fun BitwardenDateSelectButton( currentZonedDateTime: ZonedDateTime, formatPattern: String, - onDateSelect: (millis: Long) -> Unit, + onDateSelect: (ZonedDateTime) -> Unit, modifier: Modifier = Modifier, ) { var shouldShowDialog: Boolean by rememberSaveable { mutableStateOf(false) } @@ -100,7 +102,16 @@ fun BitwardenDateSelectButton( confirmButton = { TextButton( onClick = { - onDateSelect(requireNotNull(datePickerState.selectedDateMillis)) + onDateSelect( + ZonedDateTime + .ofInstant( + Instant.ofEpochMilli( + requireNotNull(datePickerState.selectedDateMillis), + ), + ZoneOffset.UTC, + ) + .withZoneSameLocal(currentZonedDateTime.zone), + ) shouldShowDialog = false }, modifier = modifier, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendDeletionDateChooser.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendDeletionDateChooser.kt index 1ddb6f133..2f57f3b82 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendDeletionDateChooser.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendDeletionDateChooser.kt @@ -1,15 +1,20 @@ package com.x8bit.bitwarden.ui.tools.feature.send +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier @@ -17,17 +22,27 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton +import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenDateSelectButton +import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTimeSelectButton import kotlinx.collections.immutable.toImmutableList +import java.time.ZonedDateTime +import java.time.temporal.ChronoUnit +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.minutes /** * Displays UX for choosing deletion date of a send. - * - * TODO: Implement custom date choosing and send choices to the VM: BIT-1090. */ +@Suppress("LongMethod") @Composable fun SendDeletionDateChooser( + currentZonedDateTime: ZonedDateTime, + dateFormatPattern: String, + timeFormatPattern: String, + onDateSelect: (ZonedDateTime) -> Unit, modifier: Modifier = Modifier, ) { + val customOption = stringResource(id = R.string.custom) val options = listOf( stringResource(id = R.string.one_hour), stringResource(id = R.string.one_day), @@ -35,7 +50,7 @@ fun SendDeletionDateChooser( stringResource(id = R.string.three_days), stringResource(id = R.string.seven_days), stringResource(id = R.string.thirty_days), - stringResource(id = R.string.custom), + customOption, ) val defaultOption = stringResource(id = R.string.seven_days) var selectedOption: String by rememberSaveable { mutableStateOf(defaultOption) } @@ -48,6 +63,52 @@ fun SendDeletionDateChooser( selectedOption = selectedOption, onOptionSelected = { selectedOption = it }, ) + + AnimatedVisibility(visible = selectedOption == customOption) { + // This tracks the date component (year, month, and day) and ignores lower level + // components. + var date: ZonedDateTime by remember { + mutableStateOf(currentZonedDateTime) + } + // This tracks just the time component (hours and minutes) and ignores the higher level + // components. 0 representing midnight and counting up from there. + var timeMillis: Long by remember { + mutableStateOf( + currentZonedDateTime.hour.hours.inWholeMilliseconds + + currentZonedDateTime.minute.minutes.inWholeMilliseconds, + ) + } + val derivedDateTimeMillis: ZonedDateTime by remember { + derivedStateOf { date.plus(timeMillis, ChronoUnit.MILLIS) } + } + + Column { + Spacer(modifier = Modifier.height(8.dp)) + Row { + BitwardenDateSelectButton( + modifier = Modifier.weight(1f), + formatPattern = dateFormatPattern, + currentZonedDateTime = currentZonedDateTime, + onDateSelect = { + date = it + onDateSelect(derivedDateTimeMillis) + }, + ) + Spacer(modifier = Modifier.width(16.dp)) + BitwardenTimeSelectButton( + modifier = Modifier.weight(1f), + formatPattern = timeFormatPattern, + currentZonedDateTime = currentZonedDateTime, + onTimeSelect = { hour, minute -> + timeMillis = hour.hours.inWholeMilliseconds + + minute.minutes.inWholeMilliseconds + onDateSelect(derivedDateTimeMillis) + }, + ) + } + } + } + Spacer(modifier = Modifier.height(4.dp)) Text( text = stringResource(id = R.string.deletion_date_info), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendContent.kt index 49029610f..8f4756d68 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendContent.kt @@ -42,6 +42,7 @@ import com.x8bit.bitwarden.ui.platform.components.SegmentedButtonState import com.x8bit.bitwarden.ui.tools.feature.send.SendDeletionDateChooser import com.x8bit.bitwarden.ui.tools.feature.send.SendExpirationDateChooser import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers +import java.time.ZonedDateTime /** * Content view for the [AddSendScreen]. @@ -154,6 +155,7 @@ fun AddSendContent( onNoteChange = addSendHandlers.onNoteChange, onHideEmailChecked = addSendHandlers.onHideEmailToggle, onDeactivateSendChecked = addSendHandlers.onDeactivateSendToggle, + onDeletionDateChange = addSendHandlers.onDeletionDateChange, ) Spacer(modifier = Modifier.height(24.dp)) @@ -180,6 +182,7 @@ private fun AddSendOptions( onNoteChange: (String) -> Unit, onHideEmailChecked: (Boolean) -> Unit, onDeactivateSendChecked: (Boolean) -> Unit, + onDeletionDateChange: (ZonedDateTime) -> Unit, ) { var isExpanded by rememberSaveable { mutableStateOf(false) } Row( @@ -223,6 +226,10 @@ private fun AddSendOptions( Column { SendDeletionDateChooser( modifier = Modifier.padding(horizontal = 16.dp), + dateFormatPattern = state.common.dateFormatPattern, + timeFormatPattern = state.common.timeFormatPattern, + currentZonedDateTime = state.common.deletionDate, + onDateSelect = onDeletionDateChange, ) Spacer(modifier = Modifier.height(8.dp)) SendExpirationDateChooser( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt index 8c425965c..d7951eb45 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt @@ -23,16 +23,12 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import java.time.Clock -import java.time.Instant +import java.time.ZonedDateTime +import java.time.temporal.ChronoUnit import javax.inject.Inject private const val KEY_STATE = "state" -/** - * The default amount of time in the future the deletion date should be set to. - */ -private const val DELETION_DATE_OFFSET_SECONDS = 604_800L - /** * View model for the new send screen. */ @@ -54,7 +50,11 @@ class AddSendViewModel @Inject constructor( noteInput = "", isHideEmailChecked = false, isDeactivateChecked = false, - deletionDate = clock.instant().plusSeconds(DELETION_DATE_OFFSET_SECONDS), + deletionDate = ZonedDateTime + .now(clock) + // We want the default time to be midnight, so we remove all values beyond days + .truncatedTo(ChronoUnit.DAYS) + .plusWeeks(1), expirationDate = null, ), selectedType = AddSendState.ViewState.Content.SendType.Text( @@ -82,6 +82,7 @@ class AddSendViewModel @Inject constructor( override fun handleAction(action: AddSendAction): Unit = when (action) { is AddSendAction.CloseClick -> handleCloseClick() + is AddSendAction.DeletionDateChange -> handleDeletionDateChange(action) AddSendAction.DismissDialogClick -> handleDismissDialogClick() is AddSendAction.SaveClick -> handleSaveClick() is AddSendAction.FileTypeClick -> handleFileTypeClick() @@ -162,6 +163,12 @@ class AddSendViewModel @Inject constructor( private fun handleCloseClick() = sendEvent(AddSendEvent.NavigateBack) + private fun handleDeletionDateChange(action: AddSendAction.DeletionDateChange) { + updateCommonContent { + it.copy(deletionDate = action.deletionDate) + } + } + private fun handleSaveClick() { onContent { content -> if (content.common.name.isBlank()) { @@ -185,7 +192,7 @@ class AddSendViewModel @Inject constructor( ) } viewModelScope.launch { - val result = vaultRepo.createSend(content.toSendView()) + val result = vaultRepo.createSend(content.toSendView(clock)) sendAction(AddSendAction.Internal.CreateSendResultReceive(result)) } } @@ -347,9 +354,13 @@ data class AddSendState( val noteInput: String, val isHideEmailChecked: Boolean, val isDeactivateChecked: Boolean, - val deletionDate: Instant, - val expirationDate: Instant?, - ) : Parcelable + val deletionDate: ZonedDateTime, + val expirationDate: ZonedDateTime?, + ) : Parcelable { + val dateFormatPattern: String get() = "M/d/yyyy" + + val timeFormatPattern: String get() = "hh:mm a" + } /** * Models what type the user is trying to send. @@ -492,6 +503,11 @@ sealed class AddSendAction { */ data class DeactivateThisSendToggle(val isChecked: Boolean) : AddSendAction() + /** + * User toggled the "deactivate this send" toggle. + */ + data class DeletionDateChange(val deletionDate: ZonedDateTime) : AddSendAction() + /** * Models actions that the [AddSendViewModel] itself might send. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/handlers/AddSendHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/handlers/AddSendHandlers.kt index 186858a3c..56329a038 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/handlers/AddSendHandlers.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/handlers/AddSendHandlers.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers import com.x8bit.bitwarden.ui.tools.feature.send.addsend.AddSendAction import com.x8bit.bitwarden.ui.tools.feature.send.addsend.AddSendViewModel +import java.time.ZonedDateTime /** * A collection of handler functions for managing actions within the context of adding @@ -19,6 +20,7 @@ data class AddSendHandlers( val onNoteChange: (String) -> Unit, val onHideEmailToggle: (Boolean) -> Unit, val onDeactivateSendToggle: (Boolean) -> Unit, + val onDeletionDateChange: (ZonedDateTime) -> Unit, ) { companion object { /** @@ -48,6 +50,9 @@ data class AddSendHandlers( onDeactivateSendToggle = { viewModel.trySendAction(AddSendAction.DeactivateThisSendToggle(it)) }, + onDeletionDateChange = { + viewModel.trySendAction(AddSendAction.DeletionDateChange(it)) + }, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensions.kt index 9557ca237..a6fb12a75 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensions.kt @@ -5,12 +5,14 @@ import com.bitwarden.core.SendTextView import com.bitwarden.core.SendType import com.bitwarden.core.SendView import com.x8bit.bitwarden.ui.tools.feature.send.addsend.AddSendState -import java.time.Instant +import java.time.Clock /** * Transforms [AddSendState] into [SendView]. */ -fun AddSendState.ViewState.Content.toSendView(): SendView = +fun AddSendState.ViewState.Content.toSendView( + clock: Clock, +): SendView = SendView( id = null, accessId = null, @@ -26,9 +28,9 @@ fun AddSendState.ViewState.Content.toSendView(): SendView = accessCount = 0U, disabled = common.isDeactivateChecked, hideEmail = common.isHideEmailChecked, - revisionDate = Instant.now(), - deletionDate = common.deletionDate, - expirationDate = common.expirationDate, + revisionDate = clock.instant(), + deletionDate = common.deletionDate.toInstant(), + expirationDate = common.expirationDate?.toInstant(), ) private fun AddSendState.ViewState.Content.SendType.toSendType(): SendType = diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt index 6bd0aeeea..dbebcde73 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt @@ -30,7 +30,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test -import java.time.Instant +import java.time.ZonedDateTime class AddSendScreenTest : BaseComposeTest() { @@ -604,7 +604,7 @@ class AddSendScreenTest : BaseComposeTest() { noteInput = "", isHideEmailChecked = false, isDeactivateChecked = false, - deletionDate = Instant.parse("2023-10-27T12:00:00Z"), + deletionDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"), expirationDate = null, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt index 56adc58c3..7628dc327 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt @@ -28,13 +28,14 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import java.time.Clock import java.time.Instant -import java.util.TimeZone +import java.time.ZoneOffset +import java.time.ZonedDateTime class AddSendViewModelTest : BaseViewModelTest() { private val clock = Clock.fixed( Instant.parse("2023-10-27T12:00:00Z"), - TimeZone.getTimeZone("UTC").toZoneId(), + ZoneOffset.UTC, ) private val mutableUserStateFlow = MutableStateFlow(DEFAULT_USER_STATE) private val authRepository: AuthRepository = mockk { @@ -89,7 +90,7 @@ class AddSendViewModelTest : BaseViewModelTest() { ) val initialState = DEFAULT_STATE.copy(viewState = viewState) val mockSendView = mockk() - every { viewState.toSendView() } returns mockSendView + every { viewState.toSendView(clock) } returns mockSendView val sendUrl = "www.test.com/send/test" val resultSendView = mockk { every { toSendUrl(DEFAULT_ENVIRONMENT_URL) } returns sendUrl @@ -117,7 +118,7 @@ class AddSendViewModelTest : BaseViewModelTest() { ) val initialState = DEFAULT_STATE.copy(viewState = viewState) val mockSendView = mockk() - every { viewState.toSendView() } returns mockSendView + every { viewState.toSendView(clock) } returns mockSendView coEvery { vaultRepository.createSend(mockSendView) } returns CreateSendResult.Error val viewModel = createViewModel(initialState) @@ -180,6 +181,27 @@ class AddSendViewModelTest : BaseViewModelTest() { assertEquals(DEFAULT_STATE, viewModel.stateFlow.value) } + @Test + fun `DeletionDateChange should store the new deletion date`() { + val viewModel = createViewModel() + val newDeletionDate = ZonedDateTime.parse("2024-09-13T00:00Z") + // DEFAULT deletion date is "2023-11-03T00:00Z" + assertEquals(DEFAULT_STATE, viewModel.stateFlow.value) + + viewModel.trySendAction(AddSendAction.DeletionDateChange(newDeletionDate)) + + assertEquals( + DEFAULT_STATE.copy( + viewState = DEFAULT_VIEW_STATE.copy( + common = DEFAULT_COMMON_STATE.copy( + deletionDate = newDeletionDate, + ), + ), + ), + viewModel.stateFlow.value, + ) + } + @Test fun `ChooseFileClick should emit ShowToast`() = runTest { val viewModel = createViewModel() @@ -353,7 +375,7 @@ class AddSendViewModelTest : BaseViewModelTest() { noteInput = "", isHideEmailChecked = false, isDeactivateChecked = false, - deletionDate = Instant.parse("2023-11-03T12:00:00Z"), + deletionDate = ZonedDateTime.parse("2023-11-03T00:00Z"), expirationDate = null, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensionsTest.kt index a1da74fa4..4cf0e4a6e 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/util/AddSendStateExtensionsTest.kt @@ -4,24 +4,15 @@ import com.bitwarden.core.SendFileView 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 io.mockk.every -import io.mockk.mockkStatic -import io.mockk.unmockkStatic -import org.junit.jupiter.api.AfterEach 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 AddSendStateExtensionsTest { - private val fixedInstant: Instant = Instant.parse("2023-10-27T12:00:00Z") - - @AfterEach - fun tearDown() { - // Some individual tests call mockkStatic so we will make sure this is always undone. - unmockkStatic(Instant::class) - } - @Test fun `toSendView should create an appropriate SendView with file type`() { val sendView = createMockSendView(number = 1, type = SendType.FILE).copy( @@ -37,12 +28,10 @@ class AddSendStateExtensionsTest { sizeName = "", ), ) - mockkStatic(Instant::class) - every { Instant.now() } returns fixedInstant val result = DEFAULT_VIEW_STATE .copy(selectedType = AddSendState.ViewState.Content.SendType.File) - .toSendView() + .toSendView(FIXED_CLOCK) assertEquals(sendView, result) } @@ -56,34 +45,35 @@ class AddSendStateExtensionsTest { accessCount = 0U, file = null, ) - mockkStatic(Instant::class) - every { Instant.now() } returns fixedInstant - val result = DEFAULT_VIEW_STATE.toSendView() + val result = DEFAULT_VIEW_STATE.toSendView(FIXED_CLOCK) assertEquals(sendView, result) } - - companion object { - private val DEFAULT_COMMON_STATE = AddSendState.ViewState.Content.Common( - name = "mockName-1", - maxAccessCount = 1, - passwordInput = "mockPassword-1", - noteInput = "mockNotes-1", - isHideEmailChecked = false, - isDeactivateChecked = false, - deletionDate = Instant.parse("2023-10-27T12:00:00Z"), - expirationDate = Instant.parse("2023-10-27T12:00:00Z"), - ) - - private val DEFAULT_SELECTED_TYPE_STATE = AddSendState.ViewState.Content.SendType.Text( - input = "mockText-1", - isHideByDefaultChecked = false, - ) - - private val DEFAULT_VIEW_STATE = AddSendState.ViewState.Content( - common = DEFAULT_COMMON_STATE, - selectedType = DEFAULT_SELECTED_TYPE_STATE, - ) - } } + +private val FIXED_CLOCK: Clock = Clock.fixed( + Instant.parse("2023-10-27T12:00:00Z"), + ZoneOffset.UTC, +) + +private val DEFAULT_COMMON_STATE = AddSendState.ViewState.Content.Common( + name = "mockName-1", + maxAccessCount = 1, + passwordInput = "mockPassword-1", + noteInput = "mockNotes-1", + isHideEmailChecked = false, + isDeactivateChecked = false, + deletionDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"), + expirationDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"), +) + +private val DEFAULT_SELECTED_TYPE_STATE = AddSendState.ViewState.Content.SendType.Text( + input = "mockText-1", + isHideByDefaultChecked = false, +) + +private val DEFAULT_VIEW_STATE = AddSendState.ViewState.Content( + common = DEFAULT_COMMON_STATE, + selectedType = DEFAULT_SELECTED_TYPE_STATE, +)