Merge pull request #7141 from element-hq/feature/bma/visualListItem

Iterate on visual list
This commit is contained in:
Benoit Marty
2026-07-03 09:35:31 +02:00
committed by GitHub
24 changed files with 117 additions and 111 deletions
@@ -31,12 +31,12 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.analytics.api.AnalyticsOptInEvents
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
import io.element.android.libraries.designsystem.background.OnboardingBackground
import io.element.android.libraries.designsystem.components.BigIcon
import io.element.android.libraries.designsystem.components.ClickableLinkText
import io.element.android.libraries.designsystem.components.visuallist.VisualList
import io.element.android.libraries.designsystem.components.visuallist.VisualListItemData
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart
@@ -129,22 +129,21 @@ private fun AnalyticsOptInContent() {
verticalBias = -0.4f
)
) {
InfoListOrganism(
VisualList(
items = persistentListOf(
InfoListItem(
VisualListItemData(
message = stringResource(id = R.string.screen_analytics_prompt_data_usage),
iconVector = CompoundIcons.CheckCircle(),
),
InfoListItem(
VisualListItemData(
message = stringResource(id = R.string.screen_analytics_prompt_third_party_sharing),
iconVector = CompoundIcons.CheckCircle(),
),
InfoListItem(
VisualListItemData(
message = stringResource(id = R.string.screen_analytics_prompt_settings),
iconVector = CompoundIcons.CheckCircle(),
),
),
textStyle = ElementTheme.typography.fontBodyLgMedium,
iconTint = ElementTheme.colors.iconSuccessPrimary,
)
}
@@ -36,10 +36,10 @@ import io.element.android.features.announcement.impl.AnnouncementState
import io.element.android.features.announcement.impl.AnnouncementStateProvider
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
import io.element.android.libraries.designsystem.components.BigIcon
import io.element.android.libraries.designsystem.components.visuallist.VisualList
import io.element.android.libraries.designsystem.components.visuallist.VisualListItemData
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
@@ -134,11 +134,9 @@ private fun FullscreenAnnouncementContent(
Column(
modifier = modifier.fillMaxSize(),
) {
InfoListOrganism(
VisualList(
modifier = Modifier.fillMaxWidth(),
items = announcement.items(),
textStyle = ElementTheme.typography.fontBodyLgMedium,
iconTint = ElementTheme.colors.iconSecondary,
iconSize = 24.dp
)
announcement.notice()?.let { notice ->
@@ -186,25 +184,25 @@ private fun Announcement.Fullscreen.icon() = when (this) {
}
@Composable
private fun Announcement.Fullscreen.items(): ImmutableList<InfoListItem> = when (this) {
private fun Announcement.Fullscreen.items(): ImmutableList<VisualListItemData> = when (this) {
Announcement.Fullscreen.Space -> persistentListOf(
InfoListItem(
VisualListItemData(
message = "View spaces you\'ve created or joined",
iconVector = CompoundIcons.VisibilityOn(),
),
InfoListItem(
VisualListItemData(
message = "Accept or decline invites to spaces",
iconVector = CompoundIcons.Email(),
),
InfoListItem(
VisualListItemData(
message = "Discover any rooms you can join in your spaces",
iconVector = CompoundIcons.Search(),
),
InfoListItem(
VisualListItemData(
message = "Join public spaces",
iconVector = CompoundIcons.Explore(),
),
InfoListItem(
VisualListItemData(
message = "Leave any spaces you’ve joined",
iconVector = CompoundIcons.Leave(),
),
@@ -48,11 +48,11 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.deactivation.impl.R
import io.element.android.features.logout.impl.ui.AccountDeactivationActionDialog
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.components.list.SwitchListItem
import io.element.android.libraries.designsystem.components.visuallist.VisualList
import io.element.android.libraries.designsystem.components.visuallist.VisualListItemData
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@@ -175,9 +175,9 @@ private fun Content(
style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary,
)
InfoListOrganism(
VisualList(
items = persistentListOf(
InfoListItem(
VisualListItemData(
message = buildAnnotatedStringWithStyledPart(
R.string.screen_deactivate_account_list_item_1,
R.string.screen_deactivate_account_list_item_1_bold_part,
@@ -194,7 +194,7 @@ private fun Content(
)
},
),
InfoListItem(
VisualListItemData(
message = stringResource(R.string.screen_deactivate_account_list_item_2),
iconComposable = {
Icon(
@@ -205,7 +205,7 @@ private fun Content(
)
},
),
InfoListItem(
VisualListItemData(
message = stringResource(R.string.screen_deactivate_account_list_item_3),
iconComposable = {
Icon(
@@ -216,7 +216,7 @@ private fun Content(
)
},
),
InfoListItem(
VisualListItemData(
message = stringResource(R.string.screen_deactivate_account_list_item_4),
iconComposable = {
Icon(
@@ -22,11 +22,11 @@ import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.securebackup.impl.R
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
import io.element.android.libraries.designsystem.components.BigIcon
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.components.visuallist.VisualList
import io.element.android.libraries.designsystem.components.visuallist.VisualListItemData
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
@@ -79,10 +79,10 @@ private fun Content() {
modifier = Modifier.padding(top = 8.dp, bottom = 40.dp),
verticalArrangement = Arrangement.spacedBy(24.dp),
) {
InfoListOrganism(
VisualList(
modifier = Modifier.fillMaxWidth(),
items = persistentListOf(
InfoListItem(
VisualListItemData(
message = stringResource(R.string.screen_encryption_reset_bullet_1),
iconComposable = {
Icon(
@@ -93,7 +93,7 @@ private fun Content() {
)
},
),
InfoListItem(
VisualListItemData(
message = stringResource(R.string.screen_encryption_reset_bullet_2),
iconComposable = {
Icon(
@@ -104,7 +104,7 @@ private fun Content() {
)
},
),
InfoListItem(
VisualListItemData(
message = stringResource(R.string.screen_encryption_reset_bullet_3),
iconComposable = {
Icon(
@@ -21,14 +21,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
import io.element.android.libraries.designsystem.components.BigIcon
import io.element.android.libraries.designsystem.components.visuallist.VisualList
import io.element.android.libraries.designsystem.components.visuallist.VisualListItemData
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
@@ -75,23 +74,21 @@ private fun SignedOutContent() {
verticalBias = -0.4f
)
) {
InfoListOrganism(
VisualList(
items = persistentListOf(
InfoListItem(
VisualListItemData(
message = stringResource(id = R.string.screen_signed_out_reason_1),
iconVector = CompoundIcons.Lock(),
),
InfoListItem(
VisualListItemData(
message = stringResource(id = R.string.screen_signed_out_reason_2),
iconVector = CompoundIcons.Devices(),
),
InfoListItem(
VisualListItemData(
message = stringResource(id = R.string.screen_signed_out_reason_3),
iconVector = CompoundIcons.Block(),
),
),
textStyle = ElementTheme.typography.fontBodyMdMedium,
iconTint = ElementTheme.colors.iconSecondary,
)
}
}
@@ -6,25 +6,19 @@
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.designsystem.atomic.organisms
package io.element.android.libraries.designsystem.components.visuallist
import androidx.annotation.DrawableRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.atomic.molecules.InfoListItemMolecule
import io.element.android.libraries.designsystem.atomic.molecules.InfoListItemPosition
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
@@ -32,14 +26,17 @@ import io.element.android.libraries.designsystem.theme.components.Text
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
/**
* Ref: https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=2001-140
*/
@Composable
fun InfoListOrganism(
items: ImmutableList<InfoListItem>,
fun VisualList(
items: ImmutableList<VisualListItemData>,
modifier: Modifier = Modifier,
backgroundColor: Color = ElementTheme.colors.bgSubtleSecondary,
iconTint: Color = LocalContentColor.current,
iconTint: Color = ElementTheme.colors.iconSecondary,
iconSize: Dp = 20.dp,
textStyle: TextStyle = LocalTextStyle.current,
textStyle: TextStyle = ElementTheme.typography.fontBodyMdMedium,
textColor: Color = ElementTheme.colors.textPrimary,
verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(4.dp),
) {
@@ -49,12 +46,12 @@ fun InfoListOrganism(
) {
for ((index, item) in items.withIndex()) {
val position = when {
items.size == 1 -> InfoListItemPosition.Single
index == 0 -> InfoListItemPosition.Top
index == items.size - 1 -> InfoListItemPosition.Bottom
else -> InfoListItemPosition.Middle
items.size == 1 -> VisualListItemPosition.Single
index == 0 -> VisualListItemPosition.Top
index == items.size - 1 -> VisualListItemPosition.Bottom
else -> VisualListItemPosition.Middle
}
InfoListItemMolecule(
VisualListItem(
message = {
if (item.message is AnnotatedString) {
Text(
@@ -96,22 +93,15 @@ fun InfoListOrganism(
}
}
data class InfoListItem(
val message: CharSequence,
@DrawableRes val iconId: Int? = null,
val iconVector: ImageVector? = null,
val iconComposable: @Composable () -> Unit = {},
)
@PreviewsDayNight
@Composable
internal fun InfoListOrganismPreview() = ElementPreview {
internal fun VisualListPreview() = ElementPreview {
val items = persistentListOf(
InfoListItem(message = "A top item"),
InfoListItem(message = "A middle item"),
InfoListItem(message = "A bottom item"),
VisualListItemData(message = "A top item"),
VisualListItemData(message = "A middle item"),
VisualListItemData(message = "A bottom item"),
)
InfoListOrganism(
VisualList(
items = items,
)
}
@@ -6,7 +6,7 @@
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.designsystem.atomic.molecules
package io.element.android.libraries.designsystem.components.visuallist
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@@ -27,10 +27,13 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
/**
* Ref: https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=2001-159
*/
@Composable
fun InfoListItemMolecule(
fun VisualListItem(
message: @Composable () -> Unit,
position: InfoListItemPosition,
position: VisualListItemPosition,
backgroundColor: Color,
modifier: Modifier = Modifier,
icon: @Composable () -> Unit = {},
@@ -38,10 +41,10 @@ fun InfoListItemMolecule(
val radius = 14.dp
val backgroundShape = remember(position) {
when (position) {
InfoListItemPosition.Single -> RoundedCornerShape(radius)
InfoListItemPosition.Top -> RoundedCornerShape(topStart = radius, topEnd = radius)
InfoListItemPosition.Middle -> RoundedCornerShape(0.dp)
InfoListItemPosition.Bottom -> RoundedCornerShape(bottomStart = radius, bottomEnd = radius)
VisualListItemPosition.Single -> RoundedCornerShape(radius)
VisualListItemPosition.Top -> RoundedCornerShape(topStart = radius, topEnd = radius)
VisualListItemPosition.Middle -> RoundedCornerShape(0.dp)
VisualListItemPosition.Bottom -> RoundedCornerShape(bottomStart = radius, bottomEnd = radius)
}
}
Row(
@@ -61,42 +64,42 @@ fun InfoListItemMolecule(
@PreviewsDayNight
@Composable
internal fun InfoListItemMoleculePreview() {
internal fun VisualListItemPreview() {
ElementPreview {
val color = ElementTheme.colors.bgSubtleSecondary
Column(
modifier = Modifier.padding(10.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
InfoListItemMolecule(
VisualListItem(
message = { Text("A single item") },
icon = { Icon(imageVector = CompoundIcons.InfoSolid(), contentDescription = null) },
position = InfoListItemPosition.Single,
position = VisualListItemPosition.Single,
backgroundColor = color,
)
InfoListItemMolecule(
VisualListItem(
message = { Text("A top item") },
icon = { Icon(imageVector = CompoundIcons.InfoSolid(), contentDescription = null) },
position = InfoListItemPosition.Top,
position = VisualListItemPosition.Top,
backgroundColor = color,
)
InfoListItemMolecule(
VisualListItem(
message = { Text("A middle item") },
icon = { Icon(imageVector = CompoundIcons.InfoSolid(), contentDescription = null) },
position = InfoListItemPosition.Middle,
position = VisualListItemPosition.Middle,
backgroundColor = color,
)
InfoListItemMolecule(
VisualListItem(
message = { Text("A bottom item") },
icon = { Icon(imageVector = CompoundIcons.InfoSolid(), contentDescription = null) },
position = InfoListItemPosition.Bottom,
position = VisualListItemPosition.Bottom,
backgroundColor = color,
)
}
}
}
enum class InfoListItemPosition {
enum class VisualListItemPosition {
Top,
Middle,
Bottom,
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.designsystem.components.visuallist
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
data class VisualListItemData(
val message: CharSequence,
@DrawableRes val iconId: Int? = null,
val iconVector: ImageVector? = null,
val iconComposable: @Composable () -> Unit = {},
)