PM-19653: Add tooltip and subtext tupport for the switch (#4936)

This commit is contained in:
David Perez
2025-03-31 10:15:58 -05:00
committed by GitHub
parent 6369b20f18
commit efec5cb4ca
3 changed files with 146 additions and 79 deletions

View File

@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
@@ -23,7 +24,9 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.toggleableState
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.cardStyle
@@ -31,6 +34,8 @@ import com.x8bit.bitwarden.ui.platform.base.util.toAnnotatedString
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
import com.x8bit.bitwarden.ui.platform.components.divider.BitwardenHorizontalDivider
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
import com.x8bit.bitwarden.ui.platform.components.model.TooltipData
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenRowOfActions
import com.x8bit.bitwarden.ui.platform.components.toggle.color.bitwardenSwitchColors
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
@@ -42,8 +47,10 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
* @param onCheckedChange A lambda that is invoked when the switch's state changes.
* @param cardStyle Indicates the type of card style to be applied.
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
* @param subtext The text to be displayed under the [label].
* @param supportingText An optional supporting text to be displayed below the [label].
* @param contentDescription A description of the switch's UI for accessibility purposes.
* @param tooltip The data required to display a tooltip.
* @param readOnly Disables the click functionality without modifying the other UI characteristics.
* @param enabled Whether or not this switch is enabled. This is similar to setting [readOnly] but
* comes with some additional visual changes.
@@ -58,8 +65,10 @@ fun BitwardenSwitch(
onCheckedChange: ((Boolean) -> Unit)?,
cardStyle: CardStyle?,
modifier: Modifier = Modifier,
subtext: String? = null,
supportingText: String? = null,
contentDescription: String? = null,
tooltip: TooltipData? = null,
readOnly: Boolean = false,
enabled: Boolean = true,
actions: (@Composable RowScope.() -> Unit)? = null,
@@ -67,9 +76,11 @@ fun BitwardenSwitch(
BitwardenSwitch(
modifier = modifier,
label = label.toAnnotatedString(),
subtext = subtext,
isChecked = isChecked,
onCheckedChange = onCheckedChange,
contentDescription = contentDescription,
tooltip = tooltip,
readOnly = readOnly,
enabled = enabled,
cardStyle = cardStyle,
@@ -98,8 +109,10 @@ fun BitwardenSwitch(
* @param onCheckedChange A lambda that is invoked when the switch's state changes.
* @param cardStyle Indicates the type of card style to be applied.
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
* @param subtext The text to be displayed under the [label].
* @param supportingText An optional supporting text to be displayed below the [label].
* @param contentDescription A description of the switch's UI for accessibility purposes.
* @param tooltip The data required to display a tooltip.
* @param readOnly Disables the click functionality without modifying the other UI characteristics.
* @param enabled Whether or not this switch is enabled. This is similar to setting [readOnly] but
* comes with some additional visual changes.
@@ -114,8 +127,10 @@ fun BitwardenSwitch(
onCheckedChange: ((Boolean) -> Unit)?,
cardStyle: CardStyle?,
modifier: Modifier = Modifier,
subtext: String? = null,
supportingText: String? = null,
contentDescription: String? = null,
tooltip: TooltipData? = null,
readOnly: Boolean = false,
enabled: Boolean = true,
actions: (@Composable RowScope.() -> Unit)? = null,
@@ -123,9 +138,11 @@ fun BitwardenSwitch(
BitwardenSwitch(
modifier = modifier,
label = label,
subtext = subtext,
isChecked = isChecked,
onCheckedChange = onCheckedChange,
contentDescription = contentDescription,
tooltip = tooltip,
readOnly = readOnly,
enabled = enabled,
cardStyle = cardStyle,
@@ -154,6 +171,7 @@ fun BitwardenSwitch(
* @param onCheckedChange A lambda that is invoked when the switch's state changes.
* @param cardStyle Indicates the type of card style to be applied.
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
* @param subtext The text to be displayed under the [label].
* @param contentDescription A description of the switch's UI for accessibility purposes.
* @param readOnly Disables the click functionality without modifying the other UI characteristics.
* @param enabled Whether or not this switch is enabled. This is similar to setting [readOnly] but
@@ -170,6 +188,7 @@ fun BitwardenSwitch(
onCheckedChange: ((Boolean) -> Unit)?,
cardStyle: CardStyle?,
modifier: Modifier = Modifier,
subtext: String? = null,
contentDescription: String? = null,
readOnly: Boolean = false,
enabled: Boolean = true,
@@ -179,6 +198,7 @@ fun BitwardenSwitch(
BitwardenSwitch(
modifier = modifier,
label = label.toAnnotatedString(),
subtext = subtext,
isChecked = isChecked,
onCheckedChange = onCheckedChange,
contentDescription = contentDescription,
@@ -198,7 +218,9 @@ fun BitwardenSwitch(
* @param onCheckedChange A lambda that is invoked when the switch's state changes.
* @param cardStyle Indicates the type of card style to be applied.
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
* @param subtext The text to be displayed under the [label].
* @param contentDescription A description of the switch's UI for accessibility purposes.
* @param tooltip The data required to display a tooltip.
* @param readOnly Disables the click functionality without modifying the other UI characteristics.
* @param enabled Whether or not this switch is enabled. This is similar to setting [readOnly] but
* comes with some additional visual changes.
@@ -207,7 +229,7 @@ fun BitwardenSwitch(
* defining the layout of the actions.
* @param supportingContent A lambda containing content directly below the label.
*/
@Suppress("LongMethod")
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
fun BitwardenSwitch(
label: AnnotatedString,
@@ -215,7 +237,9 @@ fun BitwardenSwitch(
onCheckedChange: ((Boolean) -> Unit)?,
cardStyle: CardStyle?,
modifier: Modifier = Modifier,
subtext: String? = null,
contentDescription: String? = null,
tooltip: TooltipData? = null,
readOnly: Boolean = false,
enabled: Boolean = true,
supportingContentPadding: PaddingValues = PaddingValues(vertical = 12.dp, horizontal = 16.dp),
@@ -240,26 +264,50 @@ fun BitwardenSwitch(
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.defaultMinSize(minHeight = 36.dp)
.padding(horizontal = 16.dp),
modifier = Modifier.defaultMinSize(minHeight = 36.dp),
) {
Spacer(modifier = Modifier.width(width = 16.dp))
Row(
modifier = Modifier.weight(weight = 1f),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = label,
style = BitwardenTheme.typography.bodyLarge,
color = if (enabled) {
BitwardenTheme.colorScheme.text.primary
} else {
BitwardenTheme.colorScheme.filledButton.foregroundDisabled
},
modifier = Modifier.testTag(tag = "SwitchText"),
)
actions?.invoke(this)
Column(modifier = Modifier.weight(weight = 1f, fill = false)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = label,
style = BitwardenTheme.typography.bodyLarge,
color = if (enabled) {
BitwardenTheme.colorScheme.text.primary
} else {
BitwardenTheme.colorScheme.filledButton.foregroundDisabled
},
modifier = Modifier.testTag(tag = "SwitchText"),
)
tooltip?.let {
ToolTip(
tooltip = it,
isVisible = subtext != null,
size = 16.dp,
)
}
}
subtext?.let {
Spacer(modifier = Modifier.height(height = 2.dp))
Text(
text = it,
style = BitwardenTheme.typography.bodyMedium,
color = if (enabled) {
BitwardenTheme.colorScheme.text.secondary
} else {
BitwardenTheme.colorScheme.filledButton.foregroundDisabled
},
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.testTag(tag = "SwitchSubtext"),
)
}
}
tooltip?.let { ToolTip(tooltip = it, isVisible = subtext == null) }
}
Spacer(modifier = Modifier.width(width = 16.dp))
Switch(
@@ -271,20 +319,13 @@ fun BitwardenSwitch(
onCheckedChange = null,
colors = bitwardenSwitchColors(),
)
actions?.let { BitwardenRowOfActions(actions = it) }
Spacer(modifier = Modifier.width(width = if (actions == null) 16.dp else 4.dp))
}
supportingContent
?.let { content ->
Spacer(modifier = Modifier.height(height = 12.dp))
BitwardenHorizontalDivider(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp),
)
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.defaultMinSize(minHeight = 48.dp)
.padding(paddingValues = supportingContentPadding),
SupportingContent(
paddingValues = supportingContentPadding,
content = content,
)
}
@@ -292,6 +333,45 @@ fun BitwardenSwitch(
}
}
@Composable
private fun ColumnScope.SupportingContent(
paddingValues: PaddingValues,
content: @Composable ColumnScope.() -> Unit,
) {
Spacer(modifier = Modifier.height(height = 12.dp))
BitwardenHorizontalDivider(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp),
)
Column(
modifier = Modifier
.defaultMinSize(minHeight = 48.dp)
.padding(paddingValues = paddingValues),
verticalArrangement = Arrangement.Center,
content = content,
)
}
@Composable
private fun RowScope.ToolTip(
tooltip: TooltipData,
isVisible: Boolean,
size: Dp = 48.dp,
) {
if (!isVisible) return
Spacer(modifier = Modifier.width(width = 8.dp))
BitwardenStandardIconButton(
vectorIconRes = R.drawable.ic_question_circle_small,
contentDescription = tooltip.contentDescription,
onClick = tooltip.onClick,
contentColor = BitwardenTheme.colorScheme.icon.secondary,
modifier = Modifier
.size(size = size)
.testTag(tag = "SwitchTooltip"),
)
}
@Preview
@Composable
private fun BitwardenSwitch_preview() {
@@ -314,22 +394,37 @@ private fun BitwardenSwitch_preview() {
supportingText = "description",
isChecked = true,
onCheckedChange = {},
tooltip = TooltipData(
onClick = { },
contentDescription = "content description",
),
actions = {
BitwardenStandardIconButton(
vectorIconRes = R.drawable.ic_question_circle,
vectorIconRes = R.drawable.ic_generate,
contentDescription = "content description",
onClick = {},
)
},
cardStyle = CardStyle.Middle(),
)
BitwardenSwitch(
label = "Label",
supportingText = "description",
isChecked = true,
onCheckedChange = {},
tooltip = TooltipData(
onClick = { },
contentDescription = "content description",
),
cardStyle = CardStyle.Middle(),
)
BitwardenSwitch(
label = "Label",
isChecked = false,
onCheckedChange = {},
actions = {
BitwardenStandardIconButton(
vectorIconRes = R.drawable.ic_question_circle,
vectorIconRes = R.drawable.ic_generate,
contentDescription = "content description",
onClick = {},
)

View File

@@ -13,13 +13,12 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenExpandingHeader
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
import com.x8bit.bitwarden.ui.platform.components.model.TooltipData
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
@@ -79,16 +78,12 @@ fun LazyListScope.vaultAddEditAdditionalOptions(
label = stringResource(id = R.string.password_prompt),
isChecked = commonState.masterPasswordReprompt,
onCheckedChange = commonTypeHandlers.onToggleMasterPasswordReprompt,
actions = {
BitwardenStandardIconButton(
vectorIconRes = R.drawable.ic_question_circle_small,
contentDescription = stringResource(
id = R.string.master_password_re_prompt_help,
),
onClick = commonTypeHandlers.onTooltipClick,
contentColor = BitwardenTheme.colorScheme.icon.secondary,
)
},
tooltip = TooltipData(
onClick = commonTypeHandlers.onTooltipClick,
contentDescription = stringResource(
id = R.string.master_password_re_prompt_help,
),
),
cardStyle = CardStyle.Full,
modifier = Modifier
.testTag(tag = "MasterPasswordRepromptToggle")

View File

@@ -1,22 +1,17 @@
package com.x8bit.bitwarden.ui.vault.feature.addedit
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.cardStyle
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenSelectionDialog
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTextEntryDialog
@@ -25,7 +20,6 @@ import com.x8bit.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectB
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
import com.x8bit.bitwarden.ui.platform.components.row.BitwardenRowOfActions
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
@@ -155,38 +149,21 @@ private fun CustomFieldBoolean(
cardStyle: CardStyle,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.semantics(mergeDescendants = true) {}
.defaultMinSize(minHeight = 60.dp)
.cardStyle(
cardStyle = cardStyle,
onClick = { onValueChanged(!value) },
paddingEnd = 4.dp,
paddingTop = 6.dp,
paddingBottom = 6.dp,
),
verticalAlignment = Alignment.CenterVertically,
) {
BitwardenSwitch(
label = label,
isChecked = value,
onCheckedChange = null,
cardStyle = null,
modifier = Modifier.weight(1f),
)
BitwardenRowOfActions(
actions = {
BitwardenStandardIconButton(
vectorIconRes = R.drawable.ic_cog,
contentDescription = stringResource(id = R.string.edit),
onClick = onEditValue,
modifier = Modifier.testTag("CustomFieldSettingsButton"),
)
},
)
}
BitwardenSwitch(
modifier = modifier,
label = label,
isChecked = value,
onCheckedChange = onValueChanged,
cardStyle = cardStyle,
actions = {
BitwardenStandardIconButton(
vectorIconRes = R.drawable.ic_cog,
contentDescription = stringResource(id = R.string.edit),
onClick = onEditValue,
modifier = Modifier.testTag(tag = "CustomFieldSettingsButton"),
)
},
)
}
/**