mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
Add send icons to row items. (#496)
This commit is contained in:
parent
10bad26c95
commit
02c8f4bfec
12 changed files with 186 additions and 20 deletions
|
@ -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<SelectionItemData>,
|
||||
modifier: Modifier = Modifier,
|
||||
supportingLabel: String? = null,
|
||||
trailingLabelIcons: List<IconResource> = 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(
|
||||
|
|
|
@ -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) },
|
||||
|
|
|
@ -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<SendStatusIcon>,
|
||||
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 = {},
|
||||
|
|
|
@ -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<SendStatusIcon>,
|
||||
) : Parcelable {
|
||||
/**
|
||||
* Indicates the type of send this, a text or file.
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
}
|
|
@ -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<SendView>.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 },
|
||||
)
|
||||
}
|
||||
|
|
20
app/src/main/res/drawable/ic_send_disabled.xml
Normal file
20
app/src/main/res/drawable/ic_send_disabled.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportHeight="16"
|
||||
android:viewportWidth="16">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h16v16h-16z" />
|
||||
<path
|
||||
android:fillColor="#505059"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M9.222,1.66L15.774,11.817C16.441,12.85 15.587,14 14.553,14H1.447C0.412,14 -0.441,12.85 0.226,11.817L6.778,1.66C7.346,0.78 8.654,0.78 9.222,1.66ZM8.381,2.202C8.207,1.933 7.793,1.933 7.619,2.202L1.066,12.359C0.885,12.64 1.099,13 1.447,13H14.553C14.901,13 15.115,12.64 14.934,12.359L8.381,2.202Z" />
|
||||
<path
|
||||
android:fillColor="#505059"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M8.001,5.3C8.277,5.3 8.501,5.523 8.501,5.8V8.917C8.501,9.193 8.277,9.417 8.001,9.417C7.724,9.417 7.501,9.193 7.501,8.917V5.8C7.501,5.523 7.724,5.3 8.001,5.3Z" />
|
||||
<path
|
||||
android:fillColor="#505059"
|
||||
android:pathData="M8.676,11.471C8.676,11.844 8.373,12.146 8.001,12.146C7.628,12.146 7.326,11.844 7.326,11.471C7.326,11.098 7.628,10.796 8.001,10.796C8.373,10.796 8.676,11.098 8.676,11.471Z" />
|
||||
</group>
|
||||
</vector>
|
17
app/src/main/res/drawable/ic_send_expired.xml
Normal file
17
app/src/main/res/drawable/ic_send_expired.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportHeight="16"
|
||||
android:viewportWidth="16">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h16v16h-16z" />
|
||||
<path
|
||||
android:fillColor="#505059"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M8.5,4C8.776,4 9,4.224 9,4.5V8.586C9,8.984 8.842,9.365 8.561,9.647L6.854,11.354C6.658,11.549 6.342,11.549 6.146,11.354C5.951,11.158 5.951,10.842 6.146,10.647L7.854,8.939C7.947,8.846 8,8.719 8,8.586V4.5C8,4.224 8.224,4 8.5,4Z" />
|
||||
<path
|
||||
android:fillColor="#505059"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M8,15C11.866,15 15,11.866 15,8C15,4.134 11.866,1 8,1C4.134,1 1,4.134 1,8C1,11.866 4.134,15 8,15ZM8,16C12.418,16 16,12.418 16,8C16,3.582 12.418,0 8,0C3.582,0 0,3.582 0,8C0,12.418 3.582,16 8,16Z" />
|
||||
</group>
|
||||
</vector>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportHeight="20"
|
||||
android:viewportWidth="20">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h20v20h-20z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M9.871,20C7.918,20 6.01,19.414 4.387,18.315C2.764,17.216 1.498,15.654 0.751,13.827C0.004,11.999 -0.191,9.989 0.19,8.049C0.571,6.11 1.511,4.327 2.891,2.929C4.272,1.53 6.031,0.579 7.945,0.192C9.859,-0.194 12.104,0.005 13.907,0.761C15.711,1.518 17.252,2.8 18.337,4.444C19.422,6.089 20,8.022 20,10C19.997,12.651 18.956,15.193 17.105,17.067C15.255,18.942 12.488,19.997 9.871,20ZM9.871,1.25C8.162,1.25 6.492,1.763 5.072,2.724C3.652,3.685 2.545,5.052 1.891,6.651C1.237,8.25 1.067,10.009 1.4,11.707C1.733,13.404 2.556,14.963 3.763,16.187C4.971,17.411 6.51,18.244 8.185,18.581C9.861,18.919 11.856,18.746 13.434,18.083C15.012,17.421 16.361,16.299 17.31,14.861C18.259,13.422 18.766,11.73 18.766,9.999C18.763,7.679 17.852,5.455 16.233,3.815C14.614,2.174 12.16,1.252 9.871,1.25Z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M3.884,16.884L16.884,3.884L16,3L3,16L3.884,16.884Z" />
|
||||
</group>
|
||||
</vector>
|
17
app/src/main/res/drawable/ic_send_password.xml
Normal file
17
app/src/main/res/drawable/ic_send_password.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportHeight="16"
|
||||
android:viewportWidth="16">
|
||||
<group>
|
||||
<clip-path android:pathData="M0,0h16v16h-16z" />
|
||||
<path
|
||||
android:fillColor="#505059"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M10.27,7.838L10.751,7.933C11.861,8.153 13.052,7.832 13.91,6.975C15.277,5.608 15.277,3.392 13.91,2.025C12.543,0.658 10.327,0.658 8.96,2.025C8.103,2.883 7.782,4.074 8.002,5.184L8.109,5.722L1.9,11.659L1.281,14.136H3.219L3.719,12.136H5.219L5.719,10.136H7.626L10.27,7.838ZM8,11.136H6.5L6,13.136H4.5L4,15.136H0L1,11.136L7.021,5.378C6.739,3.955 7.15,2.421 8.253,1.318C10.01,-0.439 12.86,-0.439 14.617,1.318C16.374,3.076 16.374,5.925 14.617,7.682C13.514,8.785 11.98,9.196 10.557,8.914L8,11.136Z" />
|
||||
<path
|
||||
android:fillColor="#505059"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M11.789,3.44C11.593,3.635 11.593,3.951 11.789,4.147C11.984,4.342 12.3,4.342 12.496,4.147C12.691,3.951 12.691,3.635 12.496,3.44C12.3,3.244 11.984,3.244 11.789,3.44ZM13.203,2.732C13.789,3.318 13.789,4.268 13.203,4.854C12.617,5.44 11.667,5.44 11.082,4.854C10.496,4.268 10.496,3.318 11.082,2.732C11.667,2.147 12.617,2.147 13.203,2.732Z" />
|
||||
</group>
|
||||
</vector>
|
|
@ -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(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue