diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListOption.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListOption.kt new file mode 100644 index 0000000000..cfc414862f --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListOption.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components.dialogs + +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList + +/** + * Used to store the visual data for a list option. + */ +data class ListOption( + val title: String, + val subtitle: String? = null, +) + +/** Creates an immutable list of [ListOption]s from the given [values], using them as titles. */ +fun listOptionOf(vararg values: String): ImmutableList { + return values.map { ListOption(it) }.toImmutableList() +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt new file mode 100644 index 0000000000..af0bb4e1d1 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components.dialogs + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.toMutableStateList +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.airbnb.android.showkase.annotation.ShowkaseComposable +import io.element.android.libraries.designsystem.components.list.CheckboxListItem +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.theme.components.DialogPreview +import io.element.android.libraries.designsystem.theme.components.ListSupportingText +import io.element.android.libraries.designsystem.theme.components.SimpleAlertDialogContent +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MultipleSelectionDialog( + options: ImmutableList, + onConfirmClicked: (List) -> Unit, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + confirmButtonTitle: String = stringResource(CommonStrings.action_confirm), + dismissButtonTitle: String = stringResource(CommonStrings.action_cancel), + title: String? = null, + subtitle: String? = null, + initialSelection: ImmutableList = persistentListOf(), +) { + val decoratedSubtitle: @Composable (() -> Unit)? = subtitle?.let { + @Composable { + ListSupportingText( + text = it, + modifier = Modifier.padding(start = 8.dp) + ) + } + } + AlertDialog( + modifier = modifier, + onDismissRequest = onDismissRequest, + ) { + MultipleSelectionDialogContent( + title = title, + subtitle = decoratedSubtitle, + options = options, + confirmButtonTitle = confirmButtonTitle, + onConfirmClicked = onConfirmClicked, + dismissButtonTitle = dismissButtonTitle, + onDismissRequest = onDismissRequest, + initialSelected = initialSelection, + ) + } +} + +@Composable +internal fun MultipleSelectionDialogContent( + options: ImmutableList, + confirmButtonTitle: String, + onConfirmClicked: (List) -> Unit, + dismissButtonTitle: String, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + title: String? = null, + initialSelected: ImmutableList = persistentListOf(), + subtitle: @Composable (() -> Unit)? = null, +) { + val selectedOptionIndexes = remember { initialSelected.toMutableStateList() } + + fun isSelected(index: Int) = selectedOptionIndexes.any { it == index } + + SimpleAlertDialogContent( + title = title, + subtitle = subtitle, + modifier = modifier, + submitText = confirmButtonTitle, + onSubmitClicked = { + onConfirmClicked(selectedOptionIndexes.toList()) + }, + cancelText = dismissButtonTitle, + onCancelClicked = onDismissRequest, + applyPaddingToContents = false, + ) { + LazyColumn { + itemsIndexed(options) { index, option -> + CheckboxListItem( + headline = option.title, + checked = isSelected(index), + onChange = { + if (isSelected(index)) { + selectedOptionIndexes.remove(index) + } else { + selectedOptionIndexes.add(index) + } + }, + supportingText = option.subtitle, + compactLayout = true, + modifier = Modifier.padding(start = 8.dp) + ) + } + } + } +} + +@DayNightPreviews +@ShowkaseComposable(group = PreviewGroup.Dialogs) +@Composable +internal fun MultipleSelectionDialogContentPreview() { + ElementPreview(showBackground = false) { + DialogPreview { + val options = persistentListOf( + ListOption("Option 1", "Supporting line text lorem ipsum dolor sit amet, consectetur."), + ListOption("Option 2"), + ListOption("Option 3"), + ) + MultipleSelectionDialogContent( + title = "Dialog title", + options = options, + onConfirmClicked = {}, + onDismissRequest = {}, + confirmButtonTitle = "Save", + dismissButtonTitle = "Cancel", + initialSelected = persistentListOf(0), + ) + } + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt new file mode 100644 index 0000000000..5b75318136 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components.dialogs + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.airbnb.android.showkase.annotation.ShowkaseComposable +import io.element.android.libraries.designsystem.components.list.RadioButtonListItem +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.theme.components.DialogPreview +import io.element.android.libraries.designsystem.theme.components.ListSupportingText +import io.element.android.libraries.designsystem.theme.components.SimpleAlertDialogContent +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SingleSelectionDialog( + options: ImmutableList, + onOptionSelected: (Int) -> Unit, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + title: String? = null, + subtitle: String? = null, + dismissButtonTitle: String = stringResource(CommonStrings.action_cancel), + initialSelection: Int? = null, +) { + val decoratedSubtitle: @Composable (() -> Unit)? = subtitle?.let { + @Composable { + ListSupportingText( + text = it, + modifier = Modifier.padding(start = 8.dp) + ) + } + } + AlertDialog( + modifier = modifier, + onDismissRequest = onDismissRequest, + ) { + SingleSelectionDialogContent( + title = title, + subtitle = decoratedSubtitle, + options = options, + onOptionSelected = onOptionSelected, + dismissButtonTitle = dismissButtonTitle, + onDismissRequest = onDismissRequest, + initialSelection = initialSelection, + ) + } +} + +@Composable +internal fun SingleSelectionDialogContent( + options: ImmutableList, + onOptionSelected: (Int) -> Unit, + onDismissRequest: () -> Unit, + dismissButtonTitle: String, + modifier: Modifier = Modifier, + title: String? = null, + initialSelection: Int? = null, + subtitle: @Composable (() -> Unit)? = null, +) { + SimpleAlertDialogContent( + title = title, + subtitle = subtitle, + modifier = modifier, + cancelText = dismissButtonTitle, + onCancelClicked = onDismissRequest, + applyPaddingToContents = false, + ) { + LazyColumn { + itemsIndexed(options) { index, option -> + RadioButtonListItem( + headline = option.title, + supportingText = option.subtitle, + selected = index == initialSelection, + onSelected = { onOptionSelected(index) }, + compactLayout = true, + modifier = Modifier.padding(start = 8.dp) + ) + } + } + } +} + +@DayNightPreviews +@ShowkaseComposable(group = PreviewGroup.Dialogs) +@Composable +internal fun SingleSelectionDialogContentPreview() { + ElementPreview(showBackground = false) { + DialogPreview { + val options = persistentListOf( + ListOption("Option 1"), + ListOption("Option 2"), + ListOption("Option 3"), + ) + SingleSelectionDialogContent( + title = "Dialog title", + options = options, + onOptionSelected = {}, + onDismissRequest = {}, + dismissButtonTitle = "Cancel", + initialSelection = 0 + ) + } + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/CheckboxListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/CheckboxListItem.kt new file mode 100644 index 0000000000..d2bc30262a --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/CheckboxListItem.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components.list + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun CheckboxListItem( + headline: String, + checked: Boolean, + onChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + supportingText: String? = null, + trailingContent: ListItemContent? = null, + enabled: Boolean = true, + style: ListItemStyle = ListItemStyle.Default, + compactLayout: Boolean = false, +) { + ListItem( + modifier = modifier, + headlineContent = { Text(headline) }, + supportingContent = supportingText?.let { @Composable { Text(it) } }, + leadingContent = ListItemContent.Checkbox(checked, null, enabled, compact = compactLayout), + trailingContent = trailingContent, + style = style, + enabled = enabled, + onClick = { onChange(!checked) }, + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt new file mode 100644 index 0000000000..cac8b557ee --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components.list + +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.theme.components.IconSource +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.Checkbox as CheckboxComponent +import io.element.android.libraries.designsystem.theme.components.Icon as IconComponent +import io.element.android.libraries.designsystem.theme.components.RadioButton as RadioButtonComponent +import io.element.android.libraries.designsystem.theme.components.Switch as SwitchComponent +import io.element.android.libraries.designsystem.theme.components.Text as TextComponent + +/** + * This is a helper to set default leading and trailing content for [ListItem]s. + */ +sealed interface ListItemContent { + /** + * Default Switch content for [ListItem]. + * @param checked The current state of the switch. + * @param onChange Callback when the switch is toggled: it should only be set to override the default click behaviour in the [ListItem]. + * @param enabled Whether the switch is enabled or not. + */ + data class Switch( + val checked: Boolean, + val onChange: ((Boolean) -> Unit)? = null, + val enabled: Boolean = true + ) : ListItemContent + + /** + * Default Checkbox content for [ListItem]. + * @param checked The current state of the checkbox. + * @param onChange Callback when the checkbox is toggled: it should only be set to override the default click behaviour in the [ListItem]. + * @param enabled Whether the checkbox is enabled or not. + * @param compact Reduces the size of the component to make the wrapping [ListItem] smaller. + * This is especially useful when the [ListItem] is used inside a Dialog. `false` by default. + */ + data class Checkbox( + val checked: Boolean, + val onChange: ((Boolean) -> Unit)? = null, + val enabled: Boolean = true, + val compact: Boolean = false + ) : ListItemContent + + /** + * Default RadioButton content for [ListItem]. + * @param selected The current state of the radio button. + * @param onClick Callback when the radio button is toggled: it should only be set to override the default click behaviour in the [ListItem]. + * @param enabled Whether the radio button is enabled or not. + * @param compact Reduces the size of the component to make the wrapping [ListItem] smaller. + * This is especially useful when the [ListItem] is used inside a Dialog. `false` by default. + */ + data class RadioButton( + val selected: Boolean, + val onClick: (() -> Unit)? = null, + val enabled: Boolean = true, + val compact: Boolean = false + ) : ListItemContent + + /** + * Default Icon content for [ListItem]. Sets the Icon component to a predefined size. + * @param iconSource The icon to display, using [IconSource.getPainter]. + */ + data class Icon(val iconSource: IconSource) : ListItemContent + + /** + * Default Text content for [ListItem]. Sets the Text component to a max size and clips overflow. + * @param text The text to display. + */ + data class Text(val text: String) : ListItemContent + + /** Displays any custom content. */ + data class Custom(val content: @Composable () -> Unit) : ListItemContent + + @Composable + fun View() { + when (this) { + is Switch -> SwitchComponent( + checked = checked, + onCheckedChange = onChange, + enabled = enabled + ) + is Checkbox -> CheckboxComponent( + modifier = if (compact) Modifier.size(maxCompactSize) else Modifier, + checked = checked, + onCheckedChange = onChange, + enabled = enabled + ) + is RadioButton -> RadioButtonComponent( + modifier = if (compact) Modifier.size(maxCompactSize) else Modifier, + selected = selected, + onClick = onClick, + enabled = enabled + ) + is Icon -> IconComponent( + modifier = Modifier.size(maxCompactSize), + painter = iconSource.getPainter(), + contentDescription = iconSource.contentDescription + ) + is Text -> TextComponent(modifier = Modifier.widthIn(max = 128.dp), text = text, maxLines = 1, overflow = TextOverflow.Ellipsis) + is Custom -> content() + } + } +} + +private val maxCompactSize = DpSize(24.dp, 24.dp) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/MultipleSelectionListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/MultipleSelectionListItem.kt new file mode 100644 index 0000000000..8a0bc1b4a9 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/MultipleSelectionListItem.kt @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components.list + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +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.runtime.toMutableStateList +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import io.element.android.libraries.designsystem.components.dialogs.ListOption +import io.element.android.libraries.designsystem.components.dialogs.MultipleSelectionDialog +import io.element.android.libraries.designsystem.components.dialogs.listOptionOf +import io.element.android.libraries.designsystem.preview.ElementThemedPreview +import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.Text +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +@Composable +fun MultipleSelectionListItem( + headline: String, + options: ImmutableList, + onSelectionChanged: (List) -> Unit, + resultFormatter: (List) -> String?, + modifier: Modifier = Modifier, + supportingText: String? = null, + leadingContent: ListItemContent? = null, + selected: ImmutableList = persistentListOf(), + displayResultInTrailingContent: Boolean = false, +) { + val selectedIndexes = remember(selected) { selected.toMutableStateList() } + val selectedItemsText by remember { derivedStateOf { resultFormatter(selectedIndexes) } } + + val decoratedSupportedText: @Composable (() -> Unit)? = when { + !selectedItemsText.isNullOrBlank() && !displayResultInTrailingContent -> { + @Composable { + Text(selectedItemsText!!) + } + } + supportingText != null -> { + @Composable { + Text(supportingText) + } + } + else -> null + } + + val trailingContent: ListItemContent? = if (!selectedItemsText.isNullOrBlank() && displayResultInTrailingContent) { + ListItemContent.Text(selectedItemsText!!) + } else { + null + } + + var displaySelectionDialog by rememberSaveable { mutableStateOf(false) } + + ListItem( + modifier = modifier, + headlineContent = { Text(text = headline) }, + supportingContent = decoratedSupportedText, + leadingContent = leadingContent, + trailingContent = trailingContent, + onClick = { displaySelectionDialog = true } + ) + + if (displaySelectionDialog) { + MultipleSelectionDialog( + title = headline, + options = options, + onConfirmClicked = { newSelectedIndexes -> + if (newSelectedIndexes != selectedIndexes.toList()) { + onSelectionChanged(newSelectedIndexes) + selectedIndexes.clear() + selectedIndexes.addAll(newSelectedIndexes) + } + displaySelectionDialog = false + }, + onDismissRequest = { displaySelectionDialog = false }, + initialSelection = selectedIndexes.toImmutableList(), + ) + } +} + +@Preview("Multiple selection List item - no selection", group = PreviewGroup.ListItems) +@Composable +internal fun MutipleSelectionListItemPreview() { + ElementThemedPreview { + val options = listOptionOf("Option 1", "Option 2", "Option 3") + MultipleSelectionListItem( + headline = "Headline", + options = options, + onSelectionChanged = {}, + supportingText = "Supporting text", + resultFormatter = { result -> formatResult(result, options) }, + ) + } +} + +@Preview("Multiple selection List item - selection in supporting text", group = PreviewGroup.ListItems) +@Composable +internal fun MutipleSelectionListItemSelectedPreview() { + ElementThemedPreview { + val options = listOptionOf("Option 1", "Option 2", "Option 3") + val selected = persistentListOf(0, 2) + MultipleSelectionListItem( + headline = "Headline", + options = options, + onSelectionChanged = {}, + supportingText = "Supporting text", + resultFormatter = { + val selectedValues = formatResult(it, options) + "Selected: $selectedValues" + }, + selected = selected, + ) + } +} + +@Preview("Multiple selection List item - selection in trailing content", group = PreviewGroup.ListItems) +@Composable +internal fun MutipleSelectionListItemSelectedTrailingContentPreview() { + ElementThemedPreview { + val options = listOptionOf("Option 1", "Option 2", "Option 3") + val selected = persistentListOf(0, 2) + MultipleSelectionListItem( + headline = "Headline", + options = options, + onSelectionChanged = {}, + supportingText = "Supporting text", + resultFormatter = { selected.size.toString() }, + displayResultInTrailingContent = true, + selected = selected, + ) + } +} + +private fun formatResult(result: List, options: ImmutableList): String? { + return options.mapIndexedNotNull { index, value -> value.title.takeIf { result.contains(index) } }.joinToString(", ").takeIf { it.isNotEmpty() } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/RadioButtonListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/RadioButtonListItem.kt new file mode 100644 index 0000000000..fff038121e --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/RadioButtonListItem.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components.list + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun RadioButtonListItem( + headline: String, + selected: Boolean, + onSelected: () -> Unit, + modifier: Modifier = Modifier, + supportingText: String? = null, + trailingContent: ListItemContent? = null, + style: ListItemStyle = ListItemStyle.Default, + enabled: Boolean = true, + compactLayout: Boolean = false, +) { + ListItem( + modifier = modifier, + headlineContent = { Text(headline) }, + supportingContent = supportingText?.let { @Composable { Text(it) } }, + leadingContent = ListItemContent.RadioButton(selected, null, enabled, compact = compactLayout), + trailingContent = trailingContent, + style = style, + enabled = enabled, + onClick = onSelected, + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt new file mode 100644 index 0000000000..01dff71991 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components.list + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import io.element.android.libraries.designsystem.components.dialogs.ListOption +import io.element.android.libraries.designsystem.components.dialogs.SingleSelectionDialog +import io.element.android.libraries.designsystem.components.dialogs.listOptionOf +import io.element.android.libraries.designsystem.preview.ElementThemedPreview +import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.Text +import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.seconds + +@Composable +fun SingleSelectionListItem( + headline: String, + options: ImmutableList, + onSelectionChanged: (Int) -> Unit, + modifier: Modifier = Modifier, + supportingText: String? = null, + leadingContent: ListItemContent? = null, + resultFormatter: (Int) -> String? = { options.getOrNull(it)?.title }, + selected: Int? = null, + displayResultInTrailingContent: Boolean = false, +) { + val coroutineScope = rememberCoroutineScope() + + var selectedIndex by rememberSaveable(selected) { mutableStateOf(selected) } + val selectedItem by remember { derivedStateOf { selectedIndex?.let { resultFormatter(it) } } } + val decoratedSupportedText: @Composable (() -> Unit)? = if (!selectedItem.isNullOrBlank() && !displayResultInTrailingContent) { + @Composable { + Text(selectedItem!!) + } + } else { + supportingText?.let { + @Composable { + Text(it) + } + } + } + val trailingContent: ListItemContent? = if (!selectedItem.isNullOrBlank() && displayResultInTrailingContent) { + ListItemContent.Text(selectedItem!!) + } else { + null + } + + var displaySelectionDialog by rememberSaveable { mutableStateOf(false) } + + ListItem( + modifier = modifier, + headlineContent = { Text(text = headline) }, + supportingContent = decoratedSupportedText, + leadingContent = leadingContent, + trailingContent = trailingContent, + onClick = { displaySelectionDialog = true } + ) + + if (displaySelectionDialog) { + SingleSelectionDialog( + title = headline, + options = options, + onOptionSelected = { index -> + if (index != selectedIndex) { + onSelectionChanged(index) + selectedIndex = index + } + // Delay hiding the dialog for a bit so the new state is displayed in it before being dismissed + coroutineScope.launch { + delay(0.5.seconds) + displaySelectionDialog = false + } + }, + onDismissRequest = { displaySelectionDialog = false }, + initialSelection = selectedIndex, + ) + } +} + +@Preview("Single selection List item - no selection", group = PreviewGroup.ListItems) +@Composable +internal fun SingleSelectionListItemPreview() { + ElementThemedPreview { + SingleSelectionListItem( + headline = "Headline", + options = listOptionOf("Option 1", "Option 2", "Option 3"), + onSelectionChanged = {}, + ) + } +} + +@Preview("Single selection List item - no selection, supporting text", group = PreviewGroup.ListItems) +@Composable +internal fun SingleSelectionListItemUnselectedWithSupportingTextPreview() { + ElementThemedPreview { + SingleSelectionListItem( + headline = "Headline", + options = listOptionOf("Option 1", "Option 2", "Option 3"), + supportingText = "Supporting text", + onSelectionChanged = {}, + ) + } +} + +@Preview("Single selection List item - selection in supporting text", group = PreviewGroup.ListItems) +@Composable +internal fun SingleSelectionListItemSelectedInSupportingTextPreview() { + ElementThemedPreview { + SingleSelectionListItem( + headline = "Headline", + options = listOptionOf("Option 1", "Option 2", "Option 3"), + supportingText = "Supporting text", + onSelectionChanged = {}, + selected = 1, + ) + } +} + +@Preview("Single selection List item - selection in trailing content", group = PreviewGroup.ListItems) +@Composable +internal fun SingleSelectionListItemSelectedInTrailingContentPreview() { + ElementThemedPreview { + SingleSelectionListItem( + headline = "Headline", + options = listOptionOf("Option 1", "Option 2", "Option 3"), + supportingText = "Supporting text", + onSelectionChanged = {}, + selected = 1, + displayResultInTrailingContent = true, + ) + } +} + +@Preview("Single selection List item - custom formatter", group = PreviewGroup.ListItems) +@Composable +internal fun SingleSelectionListItemCustomFormattertPreview() { + ElementThemedPreview { + SingleSelectionListItem( + headline = "Headline", + options = listOptionOf("Option 1", "Option 2", "Option 3"), + supportingText = "Supporting text", + onSelectionChanged = {}, + resultFormatter = { "Selected index: $it"}, + selected = 1, + displayResultInTrailingContent = true, + ) + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SwitchListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SwitchListItem.kt new file mode 100644 index 0000000000..bbd7b11396 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SwitchListItem.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components.list + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun SwitchListItem( + headline: String, + value: Boolean, + onChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + supportingText: String? = null, + leadingContent: ListItemContent? = null, + enabled: Boolean = true, + style: ListItemStyle = ListItemStyle.Default, +) { + ListItem( + modifier = modifier, + headlineContent = { Text(headline) }, + supportingContent = supportingText?.let { @Composable { Text(it) } }, + leadingContent = leadingContent, + trailingContent = ListItemContent.Switch(value, null, enabled), + style = style, + enabled = enabled, + onClick = { onChange(!value) }, + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt index a3c7274c45..c9bb2d9dd5 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.designsystem.theme.components +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -55,11 +56,49 @@ internal fun SimpleAlertDialogContent( onCancelClicked: () -> Unit, modifier: Modifier = Modifier, title: String? = null, + subtitle: @Composable (() -> Unit)? = null, submitText: String? = null, onSubmitClicked: () -> Unit = {}, thirdButtonText: String? = null, onThirdButtonClicked: () -> Unit = {}, + applyPaddingToContents: Boolean = true, icon: @Composable (() -> Unit)? = null, +) { + SimpleAlertDialogContent( + content = { + Text( + text = content, + style = ElementTheme.materialTypography.bodyMedium, + ) + }, + cancelText = cancelText, + onCancelClicked = onCancelClicked, + modifier = modifier, + title = title, + subtitle = subtitle, + submitText = submitText, + onSubmitClicked = onSubmitClicked, + thirdButtonText = thirdButtonText, + onThirdButtonClicked = onThirdButtonClicked, + icon = icon, + applyPaddingToContents = applyPaddingToContents, + ) +} + +@Composable +internal fun SimpleAlertDialogContent( + cancelText: String, + onCancelClicked: () -> Unit, + modifier: Modifier = Modifier, + title: String? = null, + subtitle: @Composable (() -> Unit)? = null, + submitText: String? = null, + onSubmitClicked: () -> Unit = {}, + thirdButtonText: String? = null, + onThirdButtonClicked: () -> Unit = {}, + applyPaddingToContents: Boolean = true, + icon: @Composable (() -> Unit)? = null, + content: @Composable () -> Unit, ) { AlertDialogContent( buttons = { @@ -99,12 +138,8 @@ internal fun SimpleAlertDialogContent( ) } }, - text = { - Text( - text = content, - style = ElementTheme.materialTypography.bodyMedium, - ) - }, + subtitle = subtitle, + content = content, shape = DialogContentDefaults.shape, containerColor = DialogContentDefaults.containerColor, iconContentColor = DialogContentDefaults.iconContentColor, @@ -117,6 +152,7 @@ internal fun SimpleAlertDialogContent( // TextButtons will not consume this provided content color value, and will used their // own defined or default colors. buttonContentColor = MaterialTheme.colorScheme.primary, + applyPaddingToContents = applyPaddingToContents, ) } @@ -128,7 +164,8 @@ internal fun AlertDialogContent( buttons: @Composable () -> Unit, icon: (@Composable () -> Unit)?, title: (@Composable () -> Unit)?, - text: @Composable (() -> Unit)?, + subtitle: @Composable (() -> Unit)?, + content: @Composable (() -> Unit)?, shape: Shape, containerColor: Color, tonalElevation: Dp, @@ -137,6 +174,7 @@ internal fun AlertDialogContent( titleContentColor: Color, textContentColor: Color, modifier: Modifier = Modifier, + applyPaddingToContents: Boolean = true, ) { Surface( modifier = modifier, @@ -145,12 +183,21 @@ internal fun AlertDialogContent( tonalElevation = tonalElevation, ) { Column( - modifier = Modifier.padding(DialogContentDefaults.externalPadding) + modifier = Modifier.padding( + if (applyPaddingToContents) { + // We can just apply the same padding to the whole dialog contents + DialogContentDefaults.externalPadding + } else { + // We should only apply vertical padding in this case, every component will apply the horizontal content individually + DialogContentDefaults.externalVerticalPadding + } + ) ) { icon?.let { CompositionLocalProvider(LocalContentColor provides iconContentColor) { Box( Modifier + .then(if (applyPaddingToContents) Modifier else Modifier.padding(DialogContentDefaults.externalHorizontalPadding)) .padding(DialogContentDefaults.iconPadding) .align(Alignment.CenterHorizontally) ) { @@ -165,6 +212,12 @@ internal fun AlertDialogContent( Box( // Align the title to the center when an icon is present. Modifier + .then( + if (applyPaddingToContents) + Modifier + else + Modifier.padding(DialogContentDefaults.externalHorizontalPadding) + ) .padding(DialogContentDefaults.titlePadding) .align( if (icon == null) { @@ -179,23 +232,28 @@ internal fun AlertDialogContent( } } } - text?.let { + subtitle?.invoke() + content?.let { CompositionLocalProvider(LocalContentColor provides textContentColor) { - val textStyle = - MaterialTheme.typography.bodyMedium + val textStyle = MaterialTheme.typography.bodyMedium ProvideTextStyle(textStyle) { Box( Modifier .weight(weight = 1f, fill = false) + // We don't apply padding here if it wasn't applied to the root component, this allows us to have a full width content .padding(DialogContentDefaults.textPadding) .align(Alignment.Start) ) { - text() + content() } } } } - Box(modifier = Modifier.align(Alignment.End)) { + Box( + modifier = Modifier + .then(if (applyPaddingToContents) Modifier else Modifier.padding(DialogContentDefaults.externalHorizontalPadding)) + .align(Alignment.End) + ) { CompositionLocalProvider(LocalContentColor provides buttonContentColor) { val textStyle = MaterialTheme.typography.labelLarge @@ -304,6 +362,7 @@ private fun AlertDialogFlowRow( internal fun DialogPreview(content: @Composable () -> Unit) { Box( modifier = Modifier + .background(ElementTheme.materialColors.onSurfaceVariant) .sizeIn(minWidth = DialogMinWidth, maxWidth = DialogMaxWidth) .padding(20.dp), propagateMinConstraints = true @@ -313,8 +372,11 @@ internal fun DialogPreview(content: @Composable () -> Unit) { } internal object DialogContentDefaults { + private val externalPaddingDp = 24.dp val shape = RoundedCornerShape(12.dp) - val externalPadding = PaddingValues(all = 24.dp) + val externalPadding = PaddingValues(all = externalPaddingDp) + val externalHorizontalPadding = PaddingValues(horizontal = externalPaddingDp) + val externalVerticalPadding = PaddingValues(vertical = externalPaddingDp) val titlePadding = PaddingValues(bottom = 16.dp) val iconPadding = PaddingValues(bottom = 8.dp) val textPadding = PaddingValues(bottom = 16.dp) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt index 24de433058..af63763113 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview @@ -123,6 +124,21 @@ fun Icon( ) } +@Composable +fun Icon( + painter: Painter, + contentDescription: String?, + modifier: Modifier = Modifier, + tint: Color = LocalContentColor.current, +) { + androidx.compose.material3.Icon( + painter = painter, + contentDescription = contentDescription, + modifier = modifier, + tint = tint + ) +} + @Preview(group = PreviewGroup.Icons) @Composable internal fun IconImageVectorPreview() = diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt index e0be3486b7..6be9089d11 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.clickable import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Share +import androidx.compose.material3.ListItemColors import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle @@ -29,9 +30,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.theme.ElementTheme @@ -55,35 +58,27 @@ fun ListItem( headlineContent: @Composable () -> Unit, modifier: Modifier = Modifier, supportingContent: @Composable (() -> Unit)? = null, - leadingContent: @Composable (() -> Unit)? = null, - trailingContent: @Composable (() -> Unit)? = null, + leadingContent: ListItemContent? = null, + trailingContent: ListItemContent? = null, style: ListItemStyle = ListItemStyle.Default, enabled: Boolean = true, onClick: (() -> Unit)? = null, ) { - val headlineColor = if (enabled) when (style) { - ListItemStyle.Destructive -> ElementTheme.colors.textCriticalPrimary - else -> ElementTheme.colors.textPrimary - } else { - // We cannot apply a disabled color by default: https://issuetracker.google.com/issues/280480132 - ElementTheme.colors.textDisabled - } + val colors = ListItemDefaults.colors( + containerColor = Color.Transparent, + headlineColor = style.headlineColor(), + leadingIconColor = style.leadingIconColor(), + trailingIconColor = style.trailingIconColor(), + supportingColor = style.supportingTextColor(), + disabledHeadlineColor = ListItemDefaultColors.headlineDisabled, + disabledLeadingIconColor = ListItemDefaultColors.iconDisabled, + disabledTrailingIconColor = ListItemDefaultColors.iconDisabled, + ) - val supportingContentColor = if (enabled) { - ElementTheme.materialColors.onSurfaceVariant - } else { - // We cannot apply a disabled color by default: https://issuetracker.google.com/issues/280480132 - ElementTheme.colors.textDisabled - } - - val leadingTrailingContentColor = if (enabled) when (style) { - ListItemStyle.Primary -> ElementTheme.colors.iconPrimary - ListItemStyle.Destructive -> ElementTheme.colors.iconCriticalPrimary - else -> ElementTheme.colors.iconTertiary - } else { - // We cannot apply a disabled color by default: https://issuetracker.google.com/issues/280480132 - ElementTheme.colors.iconDisabled - } + // We cannot just pass the disabled colors, they must be set manually: https://issuetracker.google.com/issues/280480132 + val headlineColor = if (enabled) colors.headlineColor else colors.disabledHeadlineColor + val leadingContentColor = if (enabled) colors.leadingIconColor else colors.disabledLeadingIconColor + val trailingContentColor = if (enabled) colors.trailingIconColor else colors.disabledTrailingIconColor val decoratedHeadlineContent: @Composable () -> Unit = { CompositionLocalProvider( @@ -97,7 +92,6 @@ fun ListItem( { CompositionLocalProvider( LocalTextStyle provides ElementTheme.materialTypography.bodyMedium, - LocalContentColor provides supportingContentColor, ) { content() } @@ -106,31 +100,31 @@ fun ListItem( val decoratedLeadingContent: (@Composable () -> Unit)? = leadingContent?.let { content -> { CompositionLocalProvider( - LocalContentColor provides leadingTrailingContentColor, + LocalContentColor provides leadingContentColor, ) { - content() + content.View() } } } val decoratedTrailingContent: (@Composable () -> Unit)? = trailingContent?.let { content -> { CompositionLocalProvider( - LocalContentColor provides leadingTrailingContentColor, LocalTextStyle provides ElementTheme.typography.fontBodyMdRegular, + LocalContentColor provides trailingContentColor, ) { - content() + content.View() } } } androidx.compose.material3.ListItem( headlineContent = decoratedHeadlineContent, - modifier = modifier.clickable(enabled = enabled && onClick != null, onClick = onClick ?: {}), + modifier = Modifier.clickable(enabled = enabled && onClick != null, onClick = onClick ?: {}).then(modifier), overlineContent = null, supportingContent = decoratedSupportingContent, leadingContent = decoratedLeadingContent, trailingContent = decoratedTrailingContent, - colors = ListItemDefaults.colors(), // These aren't really used since we need the workaround for the disabled state color + colors = colors, tonalElevation = 0.dp, shadowElevation = 0.dp, ) @@ -143,6 +137,47 @@ sealed interface ListItemStyle { object Default : ListItemStyle object Primary: ListItemStyle object Destructive : ListItemStyle + + @Composable fun headlineColor() = when (this) { + Default, Primary -> ListItemDefaultColors.headline + Destructive -> ElementTheme.colors.textCriticalPrimary + } + + @Composable fun supportingTextColor() = when (this) { + Default, Primary -> ListItemDefaultColors.supportingText + // FIXME once we have a defined color for this value + Destructive -> ElementTheme.colors.textCriticalPrimary.copy(alpha = 0.8f) + } + + @Composable fun leadingIconColor() = when (this) { + Default -> ListItemDefaultColors.icon + Primary -> ElementTheme.colors.iconPrimary + Destructive -> ElementTheme.colors.iconCriticalPrimary + } + + @Composable fun trailingIconColor() = when (this) { + Default -> ListItemDefaultColors.icon + Primary -> ElementTheme.colors.iconPrimary + Destructive -> ElementTheme.colors.iconCriticalPrimary + } +} + +object ListItemDefaultColors { + val headline: Color @Composable get() = ElementTheme.colors.textPrimary + val headlineDisabled: Color @Composable get() = ElementTheme.colors.textDisabled + val supportingText: Color @Composable get() = ElementTheme.materialColors.onSurfaceVariant + val icon: Color @Composable get() = ElementTheme.colors.iconTertiary + val iconDisabled: Color @Composable get() = ElementTheme.colors.iconDisabled + + val colors: ListItemColors @Composable get() = ListItemDefaults.colors( + headlineColor = headline, + supportingColor = supportingText, + leadingIconColor = icon, + trailingIconColor = icon, + disabledHeadlineColor = headlineDisabled, + disabledLeadingIconColor = iconDisabled, + disabledTrailingIconColor = iconDisabled, + ) } // region: Simple list item @@ -335,8 +370,9 @@ private object PreviewItems { @Composable fun ThreeLinesListItemPreview( modifier: Modifier = Modifier, - leadingContent: @Composable (() -> Unit)? = null, - trailingContent: @Composable (() -> Unit)? = null, + style: ListItemStyle = ListItemStyle.Default, + leadingContent: ListItemContent? = null, + trailingContent: ListItemContent? = null, ) { ElementThemedPreview { ListItem( @@ -344,6 +380,7 @@ private object PreviewItems { supportingContent = PreviewItems.text(), leadingContent = leadingContent, trailingContent = trailingContent, + style = style, modifier = modifier, ) } @@ -352,8 +389,9 @@ private object PreviewItems { @Composable fun TwoLinesListItemPreview( modifier: Modifier = Modifier, - leadingContent: @Composable (() -> Unit)? = null, - trailingContent: @Composable (() -> Unit)? = null, + style: ListItemStyle = ListItemStyle.Default, + leadingContent: ListItemContent? = null, + trailingContent: ListItemContent? = null, ) { ElementThemedPreview { ListItem( @@ -361,6 +399,7 @@ private object PreviewItems { supportingContent = PreviewItems.textSingleLine(), leadingContent = leadingContent, trailingContent = trailingContent, + style = style, modifier = modifier, ) } @@ -369,9 +408,9 @@ private object PreviewItems { @Composable fun OneLineListItemPreview( modifier: Modifier = Modifier, - leadingContent: @Composable (() -> Unit)? = null, - trailingContent: @Composable (() -> Unit)? = null, style: ListItemStyle = ListItemStyle.Default, + leadingContent: ListItemContent? = null, + trailingContent: ListItemContent? = null, enabled: Boolean = true, ) { ElementThemedPreview { @@ -402,25 +441,22 @@ private object PreviewItems { } @Composable - fun checkbox() = @Composable { + fun checkbox(): ListItemContent { var checked by remember { mutableStateOf(false) } - Checkbox(checked = checked, onCheckedChange = { checked = !checked }) + return ListItemContent.Checkbox(checked = checked, onChange = { checked = !checked }) } @Composable - fun radioButton() = @Composable { + fun radioButton(): ListItemContent { var checked by remember { mutableStateOf(false) } - RadioButton(selected = checked, onClick = { checked = !checked }) + return ListItemContent.RadioButton(selected = checked, onClick = { checked = !checked }) } @Composable - fun switch() = @Composable { + fun switch() : ListItemContent { var checked by remember { mutableStateOf(false) } - Switch(checked = checked, onCheckedChange = { checked = !checked }) + return ListItemContent.Switch(checked = checked, onChange = { checked = !checked }) } - @Composable - fun icon() = @Composable { - Icon(imageVector = Icons.Outlined.Share, contentDescription = null) - } + fun icon() = ListItemContent.Icon(iconSource = IconSource.Vector(Icons.Outlined.Share)) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSection.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSection.kt index 4e51795ccd..4269ff04f8 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSection.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSection.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Share +import androidx.compose.material.icons.outlined.Share import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Composable @@ -32,6 +33,7 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.theme.ElementTheme @@ -249,7 +251,7 @@ internal fun ListSupportingTextDefaultPaddingPreview() { internal fun ListSupportingTextSmallPaddingPreview() { ElementThemedPreview { Column { - ListItem(headlineContent = { Text("A title") }, leadingContent = { Icon(Icons.Default.Share, null) }) + ListItem(headlineContent = { Text("A title") }, leadingContent = ListItemContent.Icon(IconSource.Vector(Icons.Outlined.Share))) ListSupportingText( text = "Supporting line text lorem ipsum dolor sit amet, consectetur. Read more", contentPadding = ListSupportingTextDefaults.Padding.SmallLeadingContent, @@ -263,7 +265,7 @@ internal fun ListSupportingTextSmallPaddingPreview() { internal fun ListSupportingTextLargePaddingPreview() { ElementThemedPreview { Column { - ListItem(headlineContent = { Text("A title") }, leadingContent = { Switch(checked = true, onCheckedChange = null) }) + ListItem(headlineContent = { Text("A title") }, leadingContent = ListItemContent.Switch(checked = true, onChange = {})) ListSupportingText( text = "Supporting line text lorem ipsum dolor sit amet, consectetur. Read more", contentPadding = ListSupportingTextDefaults.Padding.LargeLeadingContent, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt index 45b5eb39be..e0435ee30e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt @@ -50,7 +50,8 @@ private fun ContentToPreview() { buttons = { /*TODO*/ }, icon = { /*TODO*/ }, title = { /*TODO*/ }, - text = { DatePicker(state = state, showModeToggle = true) }, + subtitle = null, + content = { DatePicker(state = state, showModeToggle = true) }, shape = AlertDialogDefaults.shape, containerColor = AlertDialogDefaults.containerColor, tonalElevation = AlertDialogDefaults.TonalElevation, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt index 7aae42ed0e..d900dd6d8b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt @@ -39,7 +39,8 @@ internal fun TimePickerHorizontalPreview() { buttons = { /*TODO*/ }, icon = { /*TODO*/ }, title = { /*TODO*/ }, - text = { TimePicker(state = rememberTimePickerState(), layoutType = TimePickerLayoutType.Horizontal) }, + subtitle = null, + content = { TimePicker(state = rememberTimePickerState(), layoutType = TimePickerLayoutType.Horizontal) }, shape = AlertDialogDefaults.shape, containerColor = AlertDialogDefaults.containerColor, tonalElevation = AlertDialogDefaults.TonalElevation, @@ -60,7 +61,8 @@ internal fun TimePickerVerticalPreviewLight() { buttons = { /*TODO*/ }, icon = { /*TODO*/ }, title = { /*TODO*/ }, - text = { TimePicker(state = rememberTimePickerState(), layoutType = TimePickerLayoutType.Vertical) }, + subtitle = null, + content = { TimePicker(state = rememberTimePickerState(), layoutType = TimePickerLayoutType.Vertical) }, shape = AlertDialogDefaults.shape, containerColor = AlertDialogDefaults.containerColor, tonalElevation = AlertDialogDefaults.TonalElevation, @@ -85,7 +87,8 @@ internal fun TimePickerVerticalPreviewDark() { buttons = { /*TODO*/ }, icon = { /*TODO*/ }, title = { /*TODO*/ }, - text = { TimePicker(state = pickerState, layoutType = TimePickerLayoutType.Vertical) }, + subtitle = null, + content = { TimePicker(state = pickerState, layoutType = TimePickerLayoutType.Vertical) }, shape = AlertDialogDefaults.shape, containerColor = AlertDialogDefaults.containerColor, tonalElevation = AlertDialogDefaults.TonalElevation, diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ConfirmationDialog_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ConfirmationDialog_0_null,NEXUS_5,1.0,en].png index fec2a14e4d..4d9869c5eb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ConfirmationDialog_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ConfirmationDialog_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:461788974e3bee520d26dbec39b2721c8c2cc0b8b907a7850e8761784abc2792 -size 23763 +oid sha256:1e13461ce6fe7e873fa2796520cf107e6e041559b43c8d911ba78848de34b398 +size 24585 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ErrorDialog_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ErrorDialog_0_null,NEXUS_5,1.0,en].png index 40868aec6e..0146de16cc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ErrorDialog_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_ErrorDialog_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:449a17861383c164774ada4098e8bfd5cb364de211d25b83baeb930e0f40020d -size 17446 +oid sha256:eebe29b4f5b0aa38316bb54e439f9ab541185251deffbae07ba14342b453c5cb +size 17528 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_MultipleSelectionDialogContent_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_MultipleSelectionDialogContent_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d9cb1ead47 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_MultipleSelectionDialogContent_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76aadb1f17f02efa6b0dbbafec5e5a08a1bfdb08e540b494eeaa48825c7c130a +size 30155 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_RetryDialog_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_RetryDialog_0_null,NEXUS_5,1.0,en].png index 84b542ad82..915f6f8539 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_RetryDialog_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_RetryDialog_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd3224a9292b9268874f355b681f28ae8a530341fdded66c85e36db9a14818a7 -size 23194 +oid sha256:a19f93d6d31a074b2e5c1d691505881e8424361f1d244366bc6ee5fbd5de15f6 +size 23225 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_SingleSelectionDialogContent_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_SingleSelectionDialogContent_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..98594b6108 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_Dialogs_SingleSelectionDialogContent_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18f79874b2342ffe0e6cd759f381e452767c6c824069cc04441c49914b2860d8 +size 20757 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_MultipleSelectionDialogContent-N_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_MultipleSelectionDialogContent-N_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f92655c313 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_MultipleSelectionDialogContent-N_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f540b6b0f761949348fbf65596103d482b0f3c29eb8c99750756dce1c40e674e +size 28692 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_SingleSelectionDialogContent-N_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_SingleSelectionDialogContent-N_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..de25110b80 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.dialogs_null_SingleSelectionDialogContent-N_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa6c275983acd5902a73fecc03a3050f1dbf70c272557bf8240c7bc5ec6c1a45 +size 19809 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-noselection_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-noselection_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..49e9fd7ed5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-noselection_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb1e859e011c8359935b0254472be653d878146a3735196f9edee324a204b1c1 +size 14192 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-selectioninsupportingtext_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-selectioninsupportingtext_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..dbdcc65b12 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-selectioninsupportingtext_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:504265795b72916040c091b767ffd278fe273a83d4f4a1683c06d805b3e3a16d +size 17818 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ec20ff6d63 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_MultipleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a0eb132bfaf1c90c6507c12b25150fb29e260ae3bd0a5760f5fb2e9483fda26 +size 14814 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-customformatter_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-customformatter_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3d0c5e4a60 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-customformatter_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:624a00c3098b821482dcdb9529db12cfcf46dc82e136d8b255561125e932862b +size 19467 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-noselection,supportingtext_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-noselection,supportingtext_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..49e9fd7ed5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-noselection,supportingtext_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb1e859e011c8359935b0254472be653d878146a3735196f9edee324a204b1c1 +size 14192 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-noselection_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-noselection_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..229ae01f43 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-noselection_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d86d982afc4add4d3d12574814ec686ad90f20f5098b965560a7a5e413b7f1dd +size 8441 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-selectioninsupportingtext_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-selectioninsupportingtext_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..26c7c0519f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-selectioninsupportingtext_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c442cb40b7c6a26d6014005587cd618d213ccb003e8d107faf8d688f069395f3 +size 11844 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..41bc1be58d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.list_null_Listitems_SingleselectionListitem-selectionintrailingcontent_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2ffe9b836ee6dc753f7e6faf10814d7a52611cd1ff9d11a539cc045fdafc1db +size 17227 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Dialogs_ProgressDialog_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Dialogs_ProgressDialog_0_null,NEXUS_5,1.0,en].png index e7b5185fbe..fd42ba21ef 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Dialogs_ProgressDialog_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_null_Dialogs_ProgressDialog_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6695c2d908300730424b28de599a966a3a7ea44f056ac6de745c713915a6a0a5 -size 21745 +oid sha256:ee64fde90f43c95741d8f39e9bf4c770cf542dfaadaf611e98151e9419f1bf9d +size 22069 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithonlymessageandokbutton_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithonlymessageandokbutton_0_null,NEXUS_5,1.0,en].png index 14cb782be4..32d5b45a6b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithonlymessageandokbutton_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithonlymessageandokbutton_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:033fc99c9948b786db535249490b123f9830b4c0c6b8d29d40188e917b8e1209 -size 51022 +oid sha256:a38a4a04c3527e9d1bb7efc3c4b91f6cfbd4eab93c84d316e68a4fc558e137cd +size 51533 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitle,iconandokbutton_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitle,iconandokbutton_0_null,NEXUS_5,1.0,en].png index e8f354cd2c..30ca4d7948 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitle,iconandokbutton_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitle,iconandokbutton_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61c633685a0afc23e5f896bb5576658a7444b0a0fa7a1aefc4be9fae86fcd3e7 -size 57358 +oid sha256:0182810e251fe8a355d275bbe97a1f7c8f82577b7f70a769a852811f8e9ee715 +size 57958 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitleandokbutton_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitleandokbutton_0_null,NEXUS_5,1.0,en].png index 450b1e3057..d090dc2fa8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitleandokbutton_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Dialogs_Dialogwithtitleandokbutton_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92502759287057d36a3fe5532d48e911e24df080c8c1e8eab01c4e406bd95fc8 -size 55792 +oid sha256:9980d0479b405e3b3215628ae92227c02423b7839c2754bb4b230da549e82cbe +size 56305 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-largepadding_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-largepadding_0_null,NEXUS_5,1.0,en].png index ae1a8f9573..f7b0bd2921 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-largepadding_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-largepadding_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ba37602f8c2519c1854776ea349fd53fb9fb6fd4b1d94a55d297b297854656b -size 27584 +oid sha256:3b37e1793e658812995411e954e060e983e48e7d450af28e1577b2b8f6f8e30f +size 27527 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-smallpadding_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-smallpadding_0_null,NEXUS_5,1.0,en].png index 7b01286f2e..b6851470fc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-smallpadding_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_null_Listsections_Listsupportingtext-smallpadding_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d2a776e9724abd16b1692f0fdac43e39dedcd8c003128d1ea354f1f8df245ac -size 26147 +oid sha256:acb1adfada4482915a2b42b86c9b73c5815fe992647e67b065f569b45ae69779 +size 26304