mirror of
https://github.com/bitwarden/android.git
synced 2026-06-07 14:57:41 -05:00
Display attachments in the UI (#754)
This commit is contained in:
committed by
Álison Fernandes
parent
be8608e53a
commit
89fda64baa
@@ -0,0 +1,190 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.attachments
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
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.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.bottomDivider
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledTonalButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
|
||||
import com.x8bit.bitwarden.ui.vault.feature.attachments.handlers.AttachmentsHandlers
|
||||
|
||||
/**
|
||||
* The top level content UI state for the [AttachmentsScreen] when viewing a content.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun AttachmentsContent(
|
||||
viewState: AttachmentsState.ViewState.Content,
|
||||
attachmentsHandlers: AttachmentsHandlers,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
) {
|
||||
if (viewState.attachments.isEmpty()) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.no_attachments),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
} else {
|
||||
items(viewState.attachments) {
|
||||
AttachmentListEntry(
|
||||
attachmentItem = it,
|
||||
onDeleteClick = attachmentsHandlers.onDeleteClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(36.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.add_new_attachment),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.no_file_chosen),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenFilledTonalButton(
|
||||
label = stringResource(id = R.string.choose_file),
|
||||
onClick = attachmentsHandlers.onChooseFileClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.max_file_size),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 32.dp),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AttachmentListEntry(
|
||||
attachmentItem: AttachmentsState.AttachmentItem,
|
||||
onDeleteClick: (attachmentId: String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var shouldShowDeleteDialog by rememberSaveable { mutableStateOf(false) }
|
||||
if (shouldShowDeleteDialog) {
|
||||
BitwardenTwoButtonDialog(
|
||||
title = stringResource(id = R.string.delete),
|
||||
message = stringResource(id = R.string.do_you_really_want_to_delete),
|
||||
confirmButtonText = stringResource(id = R.string.delete),
|
||||
dismissButtonText = stringResource(id = R.string.cancel),
|
||||
onConfirmClick = {
|
||||
shouldShowDeleteDialog = false
|
||||
onDeleteClick(attachmentItem.id)
|
||||
},
|
||||
onDismissClick = { shouldShowDeleteDialog = false },
|
||||
onDismissRequest = { shouldShowDeleteDialog = false },
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.bottomDivider(
|
||||
paddingStart = 16.dp,
|
||||
color = MaterialTheme.colorScheme.outlineVariant,
|
||||
)
|
||||
.defaultMinSize(minHeight = 56.dp)
|
||||
.padding(vertical = 8.dp)
|
||||
.then(modifier),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = attachmentItem.title,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
Text(
|
||||
text = attachmentItem.displaySize,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
modifier = Modifier,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
IconButton(
|
||||
onClick = { shouldShowDeleteDialog = true },
|
||||
modifier = Modifier,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_trash),
|
||||
contentDescription = stringResource(id = R.string.delete),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,14 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.attachments
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -24,27 +18,45 @@ 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.components.BitwardenErrorContent
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenLoadingContent
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.NavigationIcon
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.vault.feature.attachments.handlers.AttachmentsHandlers
|
||||
|
||||
/**
|
||||
* Displays the attachments screen.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AttachmentsScreen(
|
||||
viewModel: AttachmentsViewModel = hiltViewModel(),
|
||||
intentManager: IntentManager = LocalIntentManager.current,
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val attachmentsHandlers = remember(viewModel) { AttachmentsHandlers.create(viewModel) }
|
||||
val fileChooserLauncher = intentManager.launchActivityForResult { activityResult ->
|
||||
intentManager.getFileDataFromActivityResult(activityResult)?.let {
|
||||
attachmentsHandlers.onFileChoose(it)
|
||||
}
|
||||
}
|
||||
val context = LocalContext.current
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
AttachmentsEvent.NavigateBack -> onNavigateBack()
|
||||
|
||||
AttachmentsEvent.ShowChooserSheet -> {
|
||||
fileChooserLauncher.launch(
|
||||
intentManager.createFileChooserIntent(withCameraIntents = false),
|
||||
)
|
||||
}
|
||||
|
||||
is AttachmentsEvent.ShowToast -> {
|
||||
Toast
|
||||
.makeText(context, event.message(context.resources), Toast.LENGTH_SHORT)
|
||||
@@ -53,7 +65,6 @@ fun AttachmentsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
val attachmentHandlers = remember(viewModel) { AttachmentsHandlers.create(viewModel) }
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
@@ -66,31 +77,35 @@ fun AttachmentsScreen(
|
||||
navigationIcon = NavigationIcon(
|
||||
navigationIcon = painterResource(id = R.drawable.ic_back),
|
||||
navigationIconContentDescription = stringResource(id = R.string.back),
|
||||
onNavigationIconClick = attachmentHandlers.onBackClick,
|
||||
onNavigationIconClick = attachmentsHandlers.onBackClick,
|
||||
),
|
||||
actions = {
|
||||
BitwardenTextButton(
|
||||
label = stringResource(id = R.string.save),
|
||||
onClick = attachmentHandlers.onSaveClick,
|
||||
onClick = attachmentsHandlers.onSaveClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
item {
|
||||
Text(text = "Not Yet Implemented")
|
||||
}
|
||||
val modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
when (val viewState = state.viewState) {
|
||||
is AttachmentsState.ViewState.Content -> AttachmentsContent(
|
||||
viewState = viewState,
|
||||
attachmentsHandlers = attachmentsHandlers,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
is AttachmentsState.ViewState.Error -> BitwardenErrorContent(
|
||||
message = viewState.message(),
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
AttachmentsState.ViewState.Loading -> BitwardenLoadingContent(
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,22 @@ package com.x8bit.bitwarden.ui.vault.feature.attachments
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.core.CipherView
|
||||
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.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.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.vault.feature.attachments.util.toViewState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -16,18 +28,32 @@ private const val KEY_STATE = "state"
|
||||
*/
|
||||
@HiltViewModel
|
||||
class AttachmentsViewModel @Inject constructor(
|
||||
private val vaultRepo: VaultRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<AttachmentsState, AttachmentsEvent, AttachmentsAction>(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: AttachmentsState(
|
||||
cipherId = AttachmentsArgs(savedStateHandle).cipherId,
|
||||
viewState = AttachmentsState.ViewState.Loading,
|
||||
),
|
||||
) {
|
||||
init {
|
||||
vaultRepo
|
||||
.getVaultItemStateFlow(state.cipherId)
|
||||
.map { AttachmentsAction.Internal.CipherReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handleAction(action: AttachmentsAction) {
|
||||
when (action) {
|
||||
AttachmentsAction.BackClick -> handleBackClick()
|
||||
AttachmentsAction.SaveClick -> handleSaveClick()
|
||||
AttachmentsAction.ChooseFileClick -> handleChooseFileClick()
|
||||
is AttachmentsAction.FileChoose -> handleFileChoose(action)
|
||||
is AttachmentsAction.DeleteClick -> handleDeleteClick(action)
|
||||
is AttachmentsAction.Internal -> handleInternalAction(action)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +65,83 @@ class AttachmentsViewModel @Inject constructor(
|
||||
sendEvent(AttachmentsEvent.ShowToast("Not Yet Implemented".asText()))
|
||||
// TODO: Handle saving the attachments (BIT-522)
|
||||
}
|
||||
|
||||
private fun handleChooseFileClick() {
|
||||
sendEvent(AttachmentsEvent.ShowChooserSheet)
|
||||
}
|
||||
|
||||
private fun handleFileChoose(action: AttachmentsAction.FileChoose) {
|
||||
sendEvent(AttachmentsEvent.ShowToast("Not Yet Implemented".asText()))
|
||||
// TODO: Handle choosing a file the attachments (BIT-522)
|
||||
}
|
||||
|
||||
private fun handleDeleteClick(action: AttachmentsAction.DeleteClick) {
|
||||
sendEvent(AttachmentsEvent.ShowToast("Not Yet Implemented".asText()))
|
||||
// TODO: Handle choosing a file the attachments (BIT-522)
|
||||
}
|
||||
|
||||
private fun handleInternalAction(action: AttachmentsAction.Internal) {
|
||||
when (action) {
|
||||
is AttachmentsAction.Internal.CipherReceive -> handleCipherReceive(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCipherReceive(action: AttachmentsAction.Internal.CipherReceive) {
|
||||
when (val dataState = action.cipherDataState) {
|
||||
is DataState.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = AttachmentsState.ViewState.Error(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is DataState.Loaded -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = dataState
|
||||
.data
|
||||
?.toViewState()
|
||||
?: AttachmentsState.ViewState.Error(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DataState.Loading -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(viewState = AttachmentsState.ViewState.Loading)
|
||||
}
|
||||
}
|
||||
|
||||
is DataState.NoNetwork -> mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = AttachmentsState.ViewState.Error(
|
||||
message = R.string.internet_connection_required_title
|
||||
.asText()
|
||||
.concat("\n".asText())
|
||||
.concat(R.string.internet_connection_required_message.asText()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
is DataState.Pending -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = dataState
|
||||
.data
|
||||
?.toViewState()
|
||||
?: AttachmentsState.ViewState.Error(
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +150,44 @@ class AttachmentsViewModel @Inject constructor(
|
||||
@Parcelize
|
||||
data class AttachmentsState(
|
||||
val cipherId: String,
|
||||
) : Parcelable
|
||||
val viewState: ViewState,
|
||||
) : Parcelable {
|
||||
/**
|
||||
* Represents the specific view states for the [AttachmentsScreen].
|
||||
*/
|
||||
sealed class ViewState : Parcelable {
|
||||
/**
|
||||
* Represents an error state for the [AttachmentsScreen].
|
||||
*/
|
||||
@Parcelize
|
||||
data class Error(val message: Text) : ViewState()
|
||||
|
||||
/**
|
||||
* Loading state for the [AttachmentsScreen], signifying that the content is being
|
||||
* processed.
|
||||
*/
|
||||
@Parcelize
|
||||
data object Loading : ViewState()
|
||||
|
||||
/**
|
||||
* Represents a loaded content state for the [AttachmentsScreen].
|
||||
*/
|
||||
@Parcelize
|
||||
data class Content(
|
||||
val attachments: List<AttachmentItem>,
|
||||
) : ViewState()
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an individual attachment that is already saved to the cipher.
|
||||
*/
|
||||
@Parcelize
|
||||
data class AttachmentItem(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val displaySize: String,
|
||||
) : Parcelable
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a set of events related attachments.
|
||||
@@ -58,6 +198,11 @@ sealed class AttachmentsEvent {
|
||||
*/
|
||||
data object NavigateBack : AttachmentsEvent()
|
||||
|
||||
/**
|
||||
* Show chooser sheet.
|
||||
*/
|
||||
data object ShowChooserSheet : AttachmentsEvent()
|
||||
|
||||
/**
|
||||
* Displays the given [message] as a toast.
|
||||
*/
|
||||
@@ -79,4 +224,35 @@ sealed class AttachmentsAction {
|
||||
* User clicked the save button.
|
||||
*/
|
||||
data object SaveClick : AttachmentsAction()
|
||||
|
||||
/**
|
||||
* User clicked to select a new attachment file.
|
||||
*/
|
||||
data object ChooseFileClick : AttachmentsAction()
|
||||
|
||||
/**
|
||||
* User has chosen the file attachment.
|
||||
*/
|
||||
data class FileChoose(
|
||||
val fileData: IntentManager.FileData,
|
||||
) : AttachmentsAction()
|
||||
|
||||
/**
|
||||
* User clicked delete an attachment.
|
||||
*/
|
||||
data class DeleteClick(
|
||||
val attachmentId: String,
|
||||
) : AttachmentsAction()
|
||||
|
||||
/**
|
||||
* Internal ViewModel actions.
|
||||
*/
|
||||
sealed class Internal : AttachmentsAction() {
|
||||
/**
|
||||
* The cipher data has been received.
|
||||
*/
|
||||
data class CipherReceive(
|
||||
val cipherDataState: DataState<CipherView?>,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.attachments.handlers
|
||||
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.vault.feature.attachments.AttachmentsAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.attachments.AttachmentsViewModel
|
||||
|
||||
@@ -9,6 +10,9 @@ import com.x8bit.bitwarden.ui.vault.feature.attachments.AttachmentsViewModel
|
||||
data class AttachmentsHandlers(
|
||||
val onBackClick: () -> Unit,
|
||||
val onSaveClick: () -> Unit,
|
||||
val onChooseFileClick: () -> Unit,
|
||||
val onFileChoose: (IntentManager.FileData) -> Unit,
|
||||
val onDeleteClick: (attachmentId: String) -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
@@ -19,6 +23,13 @@ data class AttachmentsHandlers(
|
||||
AttachmentsHandlers(
|
||||
onBackClick = { viewModel.trySendAction(AttachmentsAction.BackClick) },
|
||||
onSaveClick = { viewModel.trySendAction(AttachmentsAction.SaveClick) },
|
||||
onChooseFileClick = {
|
||||
viewModel.trySendAction(AttachmentsAction.ChooseFileClick)
|
||||
},
|
||||
onFileChoose = { viewModel.trySendAction(AttachmentsAction.FileChoose(it)) },
|
||||
onDeleteClick = {
|
||||
viewModel.trySendAction(AttachmentsAction.DeleteClick(it))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.attachments.util
|
||||
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.x8bit.bitwarden.ui.vault.feature.attachments.AttachmentsState
|
||||
|
||||
/**
|
||||
* Converts the [CipherView] into a [AttachmentsState.ViewState.Content].
|
||||
*/
|
||||
fun CipherView.toViewState(): AttachmentsState.ViewState.Content =
|
||||
AttachmentsState.ViewState.Content(
|
||||
attachments = this
|
||||
.attachments
|
||||
.orEmpty()
|
||||
.mapNotNull {
|
||||
val id = it.id ?: return@mapNotNull null
|
||||
AttachmentsState.AttachmentItem(
|
||||
id = id,
|
||||
title = it.fileName.orEmpty(),
|
||||
displaySize = it.sizeName.orEmpty(),
|
||||
)
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user