diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenListItem.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenListItem.kt index 73f48148ce..f6c9bc167f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenListItem.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenListItem.kt @@ -5,9 +5,11 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -23,22 +25,25 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.x8bit.bitwarden.R +import com.x8bit.bitwarden.ui.platform.components.model.IconResource import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme /** * A Composable function that displays a row item. * * @param label The primary text label to display for the item. - * @param supportingLabel An secondary text label to display beneath the label. * @param startIcon The [Painter] object used to draw the icon at the start of the item. * @param onClick The lambda to be invoked when the item is clicked. - * @param modifier An optional [Modifier] for this Composable, defaulting to an empty Modifier. - * This allows the caller to specify things like padding, size, etc. * @param selectionDataList A list of all the selection items to be displayed in the overflow * dialog. + * @param modifier An optional [Modifier] for this Composable, defaulting to an empty Modifier. + * This allows the caller to specify things like padding, size, etc. + * @param supportingLabel An optional secondary text label to display beneath the label. + * @param trailingLabelIcons An optional list of small icons to be displayed after the [label]. */ @Suppress("LongMethod") @Composable @@ -49,6 +54,7 @@ fun BitwardenListItem( selectionDataList: List, modifier: Modifier = Modifier, supportingLabel: String? = null, + trailingLabelIcons: List = emptyList(), ) { var shouldShowDialog by remember { mutableStateOf(false) } Row( @@ -72,11 +78,28 @@ fun BitwardenListItem( ) Column(modifier = Modifier.weight(1f)) { - Text( - text = label, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface, - ) + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = label, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(weight = 1f, fill = false), + ) + + trailingLabelIcons.forEach { + Spacer(modifier = Modifier.width(8.dp)) + Icon( + painter = it.iconPainter, + contentDescription = it.contentDescription, + tint = MaterialTheme.colorScheme.secondary, + modifier = Modifier.size(16.dp), + ) + } + } supportingLabel?.let { Text( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendContent.kt index 607d3975ab..ef67c56735 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendContent.kt @@ -78,6 +78,7 @@ fun SendContent( startIcon = painterResource(id = it.type.iconRes), label = it.name, supportingLabel = it.deletionDate, + trailingLabelIcons = it.iconList, onClick = { sendHandlers.onSendClick(it) }, onCopyClick = { sendHandlers.onCopySendClick(it) }, onEditClick = { sendHandlers.onEditSendClick(it) }, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendListItem.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendListItem.kt index dd465bf031..a8506bd3d9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendListItem.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendListItem.kt @@ -9,7 +9,9 @@ import androidx.compose.ui.tooling.preview.Preview import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.components.BitwardenListItem import com.x8bit.bitwarden.ui.platform.components.SelectionItemData +import com.x8bit.bitwarden.ui.platform.components.model.IconResource import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme +import com.x8bit.bitwarden.ui.tools.feature.send.model.SendStatusIcon /** * A Composable function that displays a row send item. @@ -30,6 +32,7 @@ fun SendListItem( label: String, supportingLabel: String, startIcon: Painter, + trailingLabelIcons: List, onClick: () -> Unit, onEditClick: () -> Unit, onCopyClick: () -> Unit, @@ -40,6 +43,12 @@ fun SendListItem( label = label, supportingLabel = supportingLabel, startIcon = startIcon, + trailingLabelIcons = trailingLabelIcons.map { + IconResource( + iconPainter = painterResource(it.iconRes), + contentDescription = it.contentDescription(), + ) + }, onClick = onClick, selectionDataList = listOf( SelectionItemData( @@ -67,6 +76,7 @@ private fun SendListItem_preview() { label = "Sample Label", supportingLabel = "Jan 3, 2024, 10:35 AM", startIcon = painterResource(id = R.drawable.ic_send_text), + trailingLabelIcons = emptyList(), onClick = {}, onCopyClick = {}, onEditClick = {}, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendViewModel.kt index 1596142309..d63cb24a25 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/SendViewModel.kt @@ -12,6 +12,7 @@ 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.model.SendStatusIcon import com.x8bit.bitwarden.ui.tools.feature.send.util.toViewState import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemScreen import dagger.hilt.android.lifecycle.HiltViewModel @@ -201,6 +202,7 @@ data class SendState( val name: String, val deletionDate: String, val type: Type, + val iconList: List, ) : Parcelable { /** * Indicates the type of send this, a text or file. diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/model/SendStatusIcon.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/model/SendStatusIcon.kt new file mode 100644 index 0000000000..7271c4bb8e --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/model/SendStatusIcon.kt @@ -0,0 +1,31 @@ +package com.x8bit.bitwarden.ui.tools.feature.send.model + +import androidx.annotation.DrawableRes +import com.x8bit.bitwarden.R +import com.x8bit.bitwarden.ui.platform.base.util.Text +import com.x8bit.bitwarden.ui.platform.base.util.asText + +/** + * Represents the types of icons to be displayed with the send. + */ +enum class SendStatusIcon( + @DrawableRes val iconRes: Int, + val contentDescription: Text, +) { + DISABLED( + iconRes = R.drawable.ic_send_disabled, + contentDescription = R.string.disabled.asText(), + ), + PASSWORD( + iconRes = R.drawable.ic_send_password, + contentDescription = R.string.password.asText(), + ), + EXPIRED( + iconRes = R.drawable.ic_send_expired, + contentDescription = R.string.expired.asText(), + ), + MAX_ACCESS_COUNT_REACHED( + iconRes = R.drawable.ic_send_max_access_count_reached, + contentDescription = R.string.maximum_access_count_reached.asText(), + ), +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/util/SendDataExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/util/SendDataExtensions.kt index 6d1aa219f2..f9f1fbd879 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/util/SendDataExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/util/SendDataExtensions.kt @@ -5,6 +5,8 @@ import com.bitwarden.core.SendView import com.x8bit.bitwarden.data.vault.repository.model.SendData import com.x8bit.bitwarden.ui.tools.feature.generator.util.toFormattedPattern import com.x8bit.bitwarden.ui.tools.feature.send.SendState +import com.x8bit.bitwarden.ui.tools.feature.send.model.SendStatusIcon +import java.time.Instant private const val DELETION_DATE_PATTERN: String = "MMM d, uuuu, hh:mm a" @@ -22,16 +24,30 @@ private fun List.toSendContent(): SendState.ViewState.Content { return SendState.ViewState.Content( textTypeCount = this.count { it.type == SendType.TEXT }, fileTypeCount = this.count { it.type == SendType.FILE }, - sendItems = this.map { - SendState.ViewState.Content.SendItem( - id = requireNotNull(it.id), - name = it.name, - deletionDate = it.deletionDate.toFormattedPattern(DELETION_DATE_PATTERN), - type = when (it.type) { - SendType.TEXT -> SendState.ViewState.Content.SendItem.Type.TEXT - SendType.FILE -> SendState.ViewState.Content.SendItem.Type.FILE - }, - ) - }, + sendItems = this + .map { sendView -> + SendState.ViewState.Content.SendItem( + id = requireNotNull(sendView.id), + name = sendView.name, + deletionDate = sendView.deletionDate.toFormattedPattern(DELETION_DATE_PATTERN), + type = when (sendView.type) { + SendType.TEXT -> SendState.ViewState.Content.SendItem.Type.TEXT + SendType.FILE -> SendState.ViewState.Content.SendItem.Type.FILE + }, + iconList = listOfNotNull( + SendStatusIcon.EXPIRED.takeIf { + sendView.expirationDate?.isBefore(Instant.now()) == true + }, + sendView.password?.let { SendStatusIcon.PASSWORD }, + SendStatusIcon.MAX_ACCESS_COUNT_REACHED.takeIf { + sendView.maxAccessCount?.let { maxCount -> + sendView.accessCount >= maxCount + } == true + }, + SendStatusIcon.DISABLED.takeIf { sendView.disabled }, + ), + ) + } + .sortedBy { it.name }, ) } diff --git a/app/src/main/res/drawable/ic_send_disabled.xml b/app/src/main/res/drawable/ic_send_disabled.xml new file mode 100644 index 0000000000..7ddbe2ba99 --- /dev/null +++ b/app/src/main/res/drawable/ic_send_disabled.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_send_expired.xml b/app/src/main/res/drawable/ic_send_expired.xml new file mode 100644 index 0000000000..10851462b7 --- /dev/null +++ b/app/src/main/res/drawable/ic_send_expired.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_send_max_access_count_reached.xml b/app/src/main/res/drawable/ic_send_max_access_count_reached.xml new file mode 100644 index 0000000000..a267f54baa --- /dev/null +++ b/app/src/main/res/drawable/ic_send_max_access_count_reached.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_send_password.xml b/app/src/main/res/drawable/ic_send_password.xml new file mode 100644 index 0000000000..a66d435042 --- /dev/null +++ b/app/src/main/res/drawable/ic_send_password.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/SendScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/SendScreenTest.kt index 725442ada1..81c5a61cfe 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/SendScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/SendScreenTest.kt @@ -501,6 +501,7 @@ private val DEFAULT_SEND_ITEM: SendState.ViewState.Content.SendItem = name = "mockName-1", deletionDate = "1", type = SendState.ViewState.Content.SendItem.Type.FILE, + iconList = emptyList(), ) private val DEFAULT_CONTENT_VIEW_STATE: SendState.ViewState.Content = SendState.ViewState.Content( @@ -513,6 +514,7 @@ private val DEFAULT_CONTENT_VIEW_STATE: SendState.ViewState.Content = SendState. name = "mockName-2", deletionDate = "1", type = SendState.ViewState.Content.SendItem.Type.TEXT, + iconList = emptyList(), ), ), ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/util/SendDataExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/util/SendDataExtensionsTest.kt index 1540246338..51d9a53a3b 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/util/SendDataExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/util/SendDataExtensionsTest.kt @@ -4,6 +4,7 @@ import com.bitwarden.core.SendType import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView import com.x8bit.bitwarden.data.vault.repository.model.SendData import com.x8bit.bitwarden.ui.tools.feature.send.SendState +import com.x8bit.bitwarden.ui.tools.feature.send.model.SendStatusIcon import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach @@ -36,8 +37,8 @@ class SendDataExtensionsTest { @Test fun `toViewState should return Content when SendData is not empty`() { val list = listOf( - createMockSendView(number = 1, type = SendType.FILE), createMockSendView(number = 2, type = SendType.TEXT), + createMockSendView(number = 1, type = SendType.FILE), ) val sendData = SendData(list) @@ -53,12 +54,22 @@ class SendDataExtensionsTest { name = "mockName-1", deletionDate = "Oct 27, 2023, 12:00 PM", type = SendState.ViewState.Content.SendItem.Type.FILE, + iconList = listOf( + SendStatusIcon.EXPIRED, + SendStatusIcon.PASSWORD, + SendStatusIcon.MAX_ACCESS_COUNT_REACHED, + ), ), SendState.ViewState.Content.SendItem( id = "mockId-2", name = "mockName-2", deletionDate = "Oct 27, 2023, 12:00 PM", type = SendState.ViewState.Content.SendItem.Type.TEXT, + iconList = listOf( + SendStatusIcon.EXPIRED, + SendStatusIcon.PASSWORD, + SendStatusIcon.MAX_ACCESS_COUNT_REACHED, + ), ), ), ),