Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ fun AddEditSendContent(
policyDisablesSend: Boolean,
policySendOptionsInEffect: Boolean,
shouldHideEmailAddressToggle: Boolean,
isAddMode: Boolean,
isNewSend: Boolean,
isShared: Boolean,
addSendHandlers: AddEditSendHandlers,
permissionsManager: PermissionsManager,
Expand Down Expand Up @@ -126,7 +126,7 @@ fun AddEditSendContent(
fileType = type,
addSendHandlers = addSendHandlers,
permissionsManager = permissionsManager,
isAddMode = isAddMode,
isNewSend = isNewSend,
isShared = isShared,
)
}
Expand All @@ -142,7 +142,7 @@ fun AddEditSendContent(

Spacer(modifier = Modifier.height(height = 8.dp))

if (isAddMode) {
if (isNewSend) {
AddEditSendDeletionDateChooser(
enforcedDeletionHours = enforcedDeletionHours,
onDateSelect = addSendHandlers.onDeletionDateChange,
Expand Down Expand Up @@ -188,11 +188,11 @@ fun AddEditSendContent(
state = state,
isSendsRestrictedByPolicy = policyDisablesSend,
shouldHideEmailAddressToggle = shouldHideEmailAddressToggle,
isAddMode = isAddMode,
isNewSend = isNewSend,
addSendHandlers = addSendHandlers,
)

if (!isAddMode) {
if (!isNewSend) {
DeleteButton(
onDeleteClick = addSendHandlers.onDeleteClick,
modifier = Modifier
Expand Down Expand Up @@ -273,7 +273,7 @@ private fun ColumnScope.FileTypeContent(
fileType: AddEditSendState.ViewState.Content.SendType.File,
addSendHandlers: AddEditSendHandlers,
permissionsManager: PermissionsManager,
isAddMode: Boolean,
isNewSend: Boolean,
isShared: Boolean,
) {
val chooseFileCameraPermissionLauncher = permissionsManager.getLauncher { isGranted ->
Expand All @@ -300,7 +300,7 @@ private fun ColumnScope.FileTypeContent(
.standardHorizontalMargin()
.padding(horizontal = 16.dp),
)
} else if (isAddMode) {
} else if (isNewSend) {
fileType.name?.let {
Box(
contentAlignment = Alignment.CenterStart,
Expand Down Expand Up @@ -381,8 +381,8 @@ private fun ColumnScope.FileTypeContent(
* @param state The content state.
* @param isSendsRestrictedByPolicy When `true`, indicates that there's a policy preventing the user
* from editing or creating sends.
* @param isAddMode When `true`, indicates that we are creating a new send and `false` when editing
* an existing send.
* @param isNewSend When `true`, indicates that we are creating a new send and `false` when editing
* an existing send. Copying a send creates a new one, so it is also `true` in that case.
* @param addSendHandlers THe handlers various events.
*/
@Suppress("LongMethod")
Expand All @@ -391,7 +391,7 @@ private fun AddEditSendOptions(
state: AddEditSendState.ViewState.Content,
isSendsRestrictedByPolicy: Boolean,
shouldHideEmailAddressToggle: Boolean,
isAddMode: Boolean,
isNewSend: Boolean,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ‘

addSendHandlers: AddEditSendHandlers,
) {
var isExpanded by rememberSaveable { mutableStateOf(false) }
Expand Down Expand Up @@ -424,7 +424,7 @@ private fun AddEditSendOptions(
targetState = state
.common
.currentAccessCount
?.takeUnless { isAddMode || state.common.maxAccessCount == null },
?.takeUnless { isNewSend || state.common.maxAccessCount == null },
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ data class AddEditSendRoute(
@Serializable
enum class ModeType {
ADD,
COPY,
EDIT,
}

Expand All @@ -47,6 +48,7 @@ fun SavedStateHandle.toAddEditSendArgs(): AddEditSendArgs {
sendType = route.sendType,
addEditSendType = when (route.modeType) {
ModeType.ADD -> AddEditSendType.AddItem
ModeType.COPY -> AddEditSendType.CopyItem(sendItemId = requireNotNull(route.sendId))
ModeType.EDIT -> AddEditSendType.EditItem(sendItemId = requireNotNull(route.sendId))
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ fun AddEditSendScreen(
modifier = Modifier.testTag("SaveButton"),
)
BitwardenOverflowActionItem(
isVisible = !state.isAddMode,
isVisible = !state.isNewSend,
menuItemDataList = persistentListOfNotNull(
OverflowMenuItemData(
text = stringResource(id = BitwardenString.remove_password),
Expand Down Expand Up @@ -178,7 +178,7 @@ fun AddEditSendScreen(
policyDisablesSend = state.policyDisablesSend,
policySendOptionsInEffect = state.shouldDisplayPolicyWarning,
shouldHideEmailAddressToggle = state.shouldHideEmailAddressToggle,
isAddMode = state.isAddMode,
isNewSend = state.isNewSend,
isShared = state.isShared,
addSendHandlers = addSendHandlers,
permissionsManager = permissionsManager,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.model.AddEditSendType
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.model.AuthEmail
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.model.SendAuth
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.util.DEFAULT_DELETION_HOURS
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.util.shouldFinishOnComplete
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.util.toAddEditViewState
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.util.toEnforcedSendAuth
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.util.toSendName
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.util.toSendType
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.util.toSendView
import com.x8bit.bitwarden.ui.tools.feature.send.addedit.util.toViewState
import com.x8bit.bitwarden.ui.tools.feature.send.model.SendItemType
import com.x8bit.bitwarden.ui.tools.feature.send.util.toSendUrl
import dagger.hilt.android.lifecycle.HiltViewModel
Expand All @@ -68,11 +70,6 @@ import java.time.Instant
import java.time.temporal.ChronoUnit
import javax.inject.Inject

/**
* The deletion window applied to a new Send when no deletion date is enforced by policy (7 days).
*/
private const val DEFAULT_DELETION_HOURS: Long = 7 * 24L

private const val KEY_STATE = "state"

/**
Expand Down Expand Up @@ -102,17 +99,6 @@ private fun String?.splitToDomains(): ImmutableList<String> = this
.orEmpty()
.toImmutableList()

/**
* Returns the [SendAuth] this access type restricts a Send to, preserving [current] when it already
* matches the restricted type so that any emails already entered are kept.
*/
private fun SendAccessTypeJson.toEnforcedSendAuth(current: SendAuth): SendAuth = when (this) {
// Every option stays available, so the current selection is left alone.
SendAccessTypeJson.ANY -> current
SendAccessTypeJson.PASSWORD_PROTECTED -> SendAuth.Password
SendAccessTypeJson.SPECIFIC_PEOPLE -> current as? SendAuth.Email ?: SendAuth.Email()
}

/**
* View model for the add/edit send screen.
*/
Expand Down Expand Up @@ -196,7 +182,10 @@ class AddEditSendViewModel @Inject constructor(
},
)

is AddEditSendType.EditItem -> AddEditSendState.ViewState.Loading
// A copy is loaded from the send it is based on, the same way an edit is.
is AddEditSendType.CopyItem,
is AddEditSendType.EditItem,
-> AddEditSendState.ViewState.Loading
},
dialogState = null,
baseWebSendUrl = environmentRepo.environment.baseWebSendUrl,
Expand All @@ -212,18 +201,17 @@ class AddEditSendViewModel @Inject constructor(
) {

init {
when (val addSendType = state.addEditSendType) {
AddEditSendType.AddItem -> Unit
is AddEditSendType.EditItem -> {
state
.sourceSendItemIdOrNull
?.let { sendItemId ->
vaultRepo
.getSendStateFlow(addSendType.sendItemId)
.getSendStateFlow(sendItemId)
// We'll stop getting updates as soon as we get some loaded data.
.takeUntilLoaded()
.map { AddEditSendAction.Internal.SendDataReceive(it) }
.onEach(::sendAction)
.launchIn(viewModelScope)
}
}

generatorRepository
.generatorResultFlow
Expand Down Expand Up @@ -472,7 +460,7 @@ class AddEditSendViewModel @Inject constructor(
// created with.
val newEnforcedWhoCanAccess = effectiveSendPolicy
.whoCanAccess
?.takeIf { action.isSendControlsEnabled && state.isAddMode }
?.takeIf { action.isSendControlsEnabled && state.isNewSend }
mutableStateFlow.update { currentState ->
currentState.copy(
policyDisablesSend = effectiveSendPolicy.disableSend,
Expand Down Expand Up @@ -507,7 +495,7 @@ class AddEditSendViewModel @Inject constructor(
* unlocks.
*/
private fun AddEditSendState.newDeletionDateOrNull(newEnforcedDeletionHours: Int?): Instant? {
if (!isAddMode) return null
if (!isNewSend) return null
val hours = when {
newEnforcedDeletionHours != null -> newEnforcedDeletionHours.toLong()
// The enforcement was just dropped, so the default window is restored.
Expand Down Expand Up @@ -535,7 +523,22 @@ class AddEditSendViewModel @Inject constructor(
)
}

@Suppress("LongMethod")
/**
* Maps a loaded [SendView] into the content for whichever mode this screen is in, supplying the
* mapping with the collaborators it needs from this view model.
*
* @param currentState The state being updated, which is the in-flight value rather than the
* [state] property so that the mapping sees the same policy data as the update it belongs to.
*/
private fun SendView.toCurrentModeViewState(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this go to the extensions file too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toAddEditViewState is already in the extensions file, this is just the bit that hands it the view model's clock, environmentRepo and isHideEmailAddressEnabled so the two call sites don't have to repeat them.

currentState: AddEditSendState,
): AddEditSendState.ViewState.Content = toAddEditViewState(
state = currentState,
clock = clock,
baseWebSendUrl = environmentRepo.environment.baseWebSendUrl,
isHideEmailAddressEnabled = isHideEmailAddressEnabled,
)

private fun handleSendDataReceive(action: AddEditSendAction.Internal.SendDataReceive) {
when (val sendDataState = action.sendDataState) {
is DataState.Error -> {
Expand All @@ -553,10 +556,7 @@ class AddEditSendViewModel @Inject constructor(
it.copy(
viewState = sendDataState
.data
?.toViewState(
baseWebSendUrl = environmentRepo.environment.baseWebSendUrl,
isHideEmailAddressEnabled = isHideEmailAddressEnabled,
)
?.toCurrentModeViewState(currentState = it)
?: AddEditSendState.ViewState.Error(
message = BitwardenString.generic_error_message.asText(),
),
Expand Down Expand Up @@ -590,10 +590,7 @@ class AddEditSendViewModel @Inject constructor(
it.copy(
viewState = sendDataState
.data
?.toViewState(
baseWebSendUrl = environmentRepo.environment.baseWebSendUrl,
isHideEmailAddressEnabled = isHideEmailAddressEnabled,
)
?.toCurrentModeViewState(currentState = it)
?: AddEditSendState.ViewState.Error(
message = BitwardenString.generic_error_message.asText(),
),
Expand Down Expand Up @@ -908,7 +905,10 @@ class AddEditSendViewModel @Inject constructor(
}
viewModelScope.launch {
when (val addSendType = state.addEditSendType) {
AddEditSendType.AddItem -> {
// A copy is saved as a brand new Send, leaving the original untouched.
AddEditSendType.AddItem,
is AddEditSendType.CopyItem,
-> {
val fileType = content
.selectedType as? AddEditSendState.ViewState.Content.SendType.File
val result = vaultRepo.createSend(
Expand Down Expand Up @@ -1098,7 +1098,10 @@ data class AddEditSendState(
*/
val screenDisplayName: Text
get() = when (addEditSendType) {
AddEditSendType.AddItem -> when (sendType) {
// A copy is a new Send, so it is titled the same way as one.
AddEditSendType.AddItem,
is AddEditSendType.CopyItem,
-> when (sendType) {
SendItemType.FILE -> BitwardenString.add_file_send.asText()
SendItemType.TEXT -> BitwardenString.add_text_send.asText()
}
Expand Down Expand Up @@ -1129,9 +1132,29 @@ data class AddEditSendState(
(viewState as? ViewState.Content)?.common?.isHideEmailAddressEnabled == false

/**
* Helper to determine if the UI should display the content in add send mode.
* Helper to determine if this screen creates a brand-new Send rather than modifying an
* existing one. A copy is a new Send that happens to be pre-filled from another, so it counts
* as new despite being loaded from an existing one.
*/
val isAddMode: Boolean get() = addEditSendType is AddEditSendType.AddItem
val isNewSend: Boolean
get() = when (addEditSendType) {
AddEditSendType.AddItem,
is AddEditSendType.CopyItem,
-> true

is AddEditSendType.EditItem -> false
}

/**
* Helper to determine the ID of the send whose data this screen loads, or `null` when the send
* is being created from scratch and there is nothing to load.
*/
val sourceSendItemIdOrNull: String?
get() = when (val type = addEditSendType) {
AddEditSendType.AddItem -> null
is AddEditSendType.CopyItem -> type.sendItemId
is AddEditSendType.EditItem -> type.sendItemId
}

/**
* Helper to determine if the currently displayed send has a password already set.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ sealed class AddEditSendType : Parcelable {
@Parcelize
data object AddItem : AddEditSendType()

/**
* Indicates that we want to create a new send item pre-filled from an existing one.
*
* This creates a send like [AddItem] does, but its initial values are loaded from the send
* being copied.
*
* @param sendItemId The ID of the send item to copy.
*/
@Parcelize
data class CopyItem(val sendItemId: String) : AddEditSendType()

/**
* Indicates that we want to edit an existing send item.
*
Expand Down
Loading
Loading