PM-15038: Update custom switches to use standard component (#4327)

This commit is contained in:
David Perez 2024-11-19 09:03:23 -06:00 committed by GitHub
parent 2d15c4864f
commit ccd4fd9aba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 182 additions and 104 deletions

View file

@ -1,14 +1,11 @@
package com.x8bit.bitwarden.ui.auth.feature.createaccount
import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@ -17,31 +14,23 @@ import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.semantics.toggleableState
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
@ -76,7 +65,6 @@ import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.text.BitwardenClickableText
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
import com.x8bit.bitwarden.ui.platform.components.toggle.color.bitwardenSwitchColors
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
@ -283,6 +271,9 @@ fun CreateAccountScreen(
onPrivacyPolicyClick = remember(viewModel) {
{ viewModel.trySendAction(PrivacyPolicyClick) }
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.navigationBarsPadding())
}
@ -290,52 +281,24 @@ fun CreateAccountScreen(
}
@OptIn(ExperimentalLayoutApi::class)
@Suppress("LongMethod")
@Composable
private fun TermsAndPrivacySwitch(
isChecked: Boolean,
onCheckedChange: (Boolean) -> Unit,
onTermsClick: () -> Unit,
onPrivacyPolicyClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.semantics(mergeDescendants = true) {
testTag = "AcceptPoliciesToggle"
toggleableState = ToggleableState(isChecked)
}
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(
color = BitwardenTheme.colorScheme.background.pressed,
),
onClick = { onCheckedChange.invoke(!isChecked) },
)
.padding(start = 16.dp)
.fillMaxWidth(),
) {
Switch(
modifier = Modifier
.height(32.dp)
.width(52.dp),
checked = isChecked,
onCheckedChange = null,
colors = bitwardenSwitchColors(),
)
Column(Modifier.padding(start = 16.dp, top = 4.dp, bottom = 4.dp)) {
Text(
text = stringResource(id = R.string.accept_policies),
style = BitwardenTheme.typography.bodyLarge,
color = BitwardenTheme.colorScheme.text.primary,
)
BitwardenSwitch(
modifier = modifier,
label = stringResource(id = R.string.accept_policies),
isChecked = isChecked,
contentDescription = "AcceptPoliciesToggle",
onCheckedChange = onCheckedChange,
subContent = {
FlowRow(
horizontalArrangement = Arrangement.Start,
modifier = Modifier
.padding(end = 16.dp)
.fillMaxWidth()
.wrapContentHeight(),
modifier = Modifier.fillMaxWidth(),
) {
BitwardenClickableText(
label = stringResource(id = R.string.terms_of_service),
@ -357,6 +320,6 @@ private fun TermsAndPrivacySwitch(
innerPadding = PaddingValues(vertical = 4.dp, horizontal = 0.dp),
)
}
}
}
},
)
}

View file

@ -2,8 +2,6 @@ package com.x8bit.bitwarden.ui.auth.feature.startregistration
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -16,15 +14,12 @@ import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@ -38,8 +33,6 @@ import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.semantics.toggleableState
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
@ -70,7 +63,7 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.LoadingDialogState
import com.x8bit.bitwarden.ui.platform.components.dropdown.EnvironmentSelector
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.toggle.color.bitwardenSwitchColors
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
@ -277,7 +270,9 @@ private fun StartRegistrationContent(
isChecked = isReceiveMarketingEmailsToggled,
onCheckedChange = handler.onReceiveMarketingEmailsToggle,
onUnsubscribeClick = handler.onUnsubscribeMarketingEmailsClick,
modifier = Modifier.standardHorizontalMargin(),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
Spacer(modifier = Modifier.height(24.dp))
}
@ -361,7 +356,6 @@ private fun TermsAndPrivacyText(
}
}
@Suppress("LongMethod")
@Composable
private fun ReceiveMarketingEmailsSwitch(
isChecked: Boolean,
@ -373,7 +367,9 @@ private fun ReceiveMarketingEmailsSwitch(
@Suppress("MaxLineLength")
val annotatedLinkString = createClickableAnnotatedString(
mainString = stringResource(id = R.string.get_emails_from_bitwarden_for_announcements_advices_and_research_opportunities_unsubscribe_any_time),
mainString = stringResource(
id = R.string.get_emails_from_bitwarden_for_announcements_advices_and_research_opportunities_unsubscribe_any_time,
),
highlights = listOf(
ClickableTextHighlight(
textToHighlight = unsubscribeString,
@ -381,13 +377,9 @@ private fun ReceiveMarketingEmailsSwitch(
),
),
)
Row(
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
BitwardenSwitch(
modifier = modifier
.semantics(mergeDescendants = true) {
testTag = "ReceiveMarketingEmailsToggle"
toggleableState = ToggleableState(isChecked)
customActions = listOf(
CustomAccessibilityAction(
label = unsubscribeString,
@ -397,30 +389,12 @@ private fun ReceiveMarketingEmailsSwitch(
},
),
)
}
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(
color = BitwardenTheme.colorScheme.background.pressed,
),
onClick = { onCheckedChange.invoke(!isChecked) },
},
label = annotatedLinkString,
isChecked = isChecked,
onCheckedChange = onCheckedChange,
contentDescription = "ReceiveMarketingEmailsToggle",
)
.fillMaxWidth(),
) {
Switch(
modifier = Modifier
.height(32.dp)
.width(52.dp),
checked = isChecked,
onCheckedChange = null,
colors = bitwardenSwitchColors(),
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = annotatedLinkString,
style = BitwardenTheme.typography.bodyMedium,
)
}
}
@PreviewScreenSizes

View file

@ -22,15 +22,17 @@ import androidx.compose.ui.semantics.contentDescription
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R
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.toggle.color.bitwardenSwitchColors
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
/**
* A wide custom switch composable
* A custom switch composable
*
* @param label The descriptive text label to be displayed adjacent to the switch.
* @param isChecked The current state of the switch (either checked or unchecked).
@ -45,7 +47,6 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
* in between the [label] and the toggle. This lambda extends [RowScope], allowing flexibility in
* defining the layout of the actions.
*/
@Suppress("LongMethod")
@Composable
fun BitwardenSwitch(
label: String,
@ -57,6 +58,154 @@ fun BitwardenSwitch(
readOnly: Boolean = false,
enabled: Boolean = true,
actions: (@Composable RowScope.() -> Unit)? = null,
) {
BitwardenSwitch(
modifier = modifier,
label = label.toAnnotatedString(),
isChecked = isChecked,
onCheckedChange = onCheckedChange,
contentDescription = contentDescription,
readOnly = readOnly,
enabled = enabled,
actions = actions,
subContent = {
description?.let {
Text(
text = it,
style = BitwardenTheme.typography.bodyMedium,
color = if (enabled) {
BitwardenTheme.colorScheme.text.secondary
} else {
BitwardenTheme.colorScheme.filledButton.foregroundDisabled
},
)
}
},
)
}
/**
* A custom switch composable
*
* @param label The descriptive text label to be displayed adjacent to the switch.
* @param isChecked The current state of the switch (either checked or unchecked).
* @param onCheckedChange A lambda that is invoked when the switch's state changes.
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
* @param description An optional description label to be displayed below 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
* comes with some additional visual changes.
* @param actions A lambda containing the set of actions (usually icons or similar) to display
* in between the [label] and the toggle. This lambda extends [RowScope], allowing flexibility in
* defining the layout of the actions.
*/
@Composable
fun BitwardenSwitch(
label: AnnotatedString,
isChecked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
modifier: Modifier = Modifier,
description: String? = null,
contentDescription: String? = null,
readOnly: Boolean = false,
enabled: Boolean = true,
actions: (@Composable RowScope.() -> Unit)? = null,
) {
BitwardenSwitch(
modifier = modifier,
label = label,
isChecked = isChecked,
onCheckedChange = onCheckedChange,
contentDescription = contentDescription,
readOnly = readOnly,
enabled = enabled,
actions = actions,
subContent = {
description?.let {
Text(
text = it,
style = BitwardenTheme.typography.bodyMedium,
color = if (enabled) {
BitwardenTheme.colorScheme.text.secondary
} else {
BitwardenTheme.colorScheme.filledButton.foregroundDisabled
},
)
}
},
)
}
/**
* A custom switch composable
*
* @param label The descriptive text label to be displayed adjacent to the switch.
* @param isChecked The current state of the switch (either checked or unchecked).
* @param onCheckedChange A lambda that is invoked when the switch's state changes.
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
* @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
* comes with some additional visual changes.
* @param actions A lambda containing the set of actions (usually icons or similar) to display
* in between the [label] and the toggle. This lambda extends [RowScope], allowing flexibility in
* defining the layout of the actions.
* @param subContent A lambda containing content directly below the label.
*/
@Composable
fun BitwardenSwitch(
label: String,
isChecked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
modifier: Modifier = Modifier,
contentDescription: String? = null,
readOnly: Boolean = false,
enabled: Boolean = true,
actions: (@Composable RowScope.() -> Unit)? = null,
subContent: @Composable () -> Unit,
) {
BitwardenSwitch(
modifier = modifier,
label = label.toAnnotatedString(),
isChecked = isChecked,
onCheckedChange = onCheckedChange,
contentDescription = contentDescription,
readOnly = readOnly,
enabled = enabled,
actions = actions,
subContent = subContent,
)
}
/**
* A custom switch composable
*
* @param label The descriptive text label to be displayed adjacent to the switch.
* @param isChecked The current state of the switch (either checked or unchecked).
* @param onCheckedChange A lambda that is invoked when the switch's state changes.
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
* @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
* comes with some additional visual changes.
* @param actions A lambda containing the set of actions (usually icons or similar) to display
* in between the [label] and the toggle. This lambda extends [RowScope], allowing flexibility in
* defining the layout of the actions.
* @param subContent A lambda containing content directly below the label.
*/
@Suppress("LongMethod")
@Composable
fun BitwardenSwitch(
label: AnnotatedString,
isChecked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
modifier: Modifier = Modifier,
contentDescription: String? = null,
readOnly: Boolean = false,
enabled: Boolean = true,
actions: (@Composable RowScope.() -> Unit)? = null,
subContent: @Composable () -> Unit,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
@ -95,17 +244,7 @@ fun BitwardenSwitch(
},
modifier = Modifier.testTag(tag = "SwitchText"),
)
description?.let {
Text(
text = it,
style = BitwardenTheme.typography.bodyMedium,
color = if (enabled) {
BitwardenTheme.colorScheme.text.secondary
} else {
BitwardenTheme.colorScheme.filledButton.foregroundDisabled
},
)
}
subContent()
}
actions
@ -131,6 +270,7 @@ private fun BitwardenSwitch_preview() {
Column {
BitwardenSwitch(
label = "Label",
description = "description",
isChecked = true,
onCheckedChange = {},
)
@ -141,6 +281,7 @@ private fun BitwardenSwitch_preview() {
)
BitwardenSwitch(
label = "Label",
description = "description",
isChecked = true,
onCheckedChange = {},
actions = {