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 youve 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 = {},
)
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:44caa90be72f8ff87f5e7c9a4fa7ff18254e09e0ff8086b6cd69c6c4827f3b1a
size 82510
oid sha256:58f84b7eb0039b81f7bf1a879042095ecbd99412d48cdd8072df1486ca9d78a5
size 81677
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5657c3ce5c5928c082fde832939637cb6201d8299d31360df160a37ed70eab90
size 81391
oid sha256:e7f402ad83c4032217f22048b59de363ab1a1a19c1c4fb81ec8fd3402698c4d8
size 79607
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d6cbfa0e9218e49009e13af0d2ebffc826770f17745b6b22c18a6650bc817391
size 75776
oid sha256:08c97bcb1ad81d7aab93c619bebaac4ec9ca860b2e71ae2648ebfab41cbdeca8
size 74832
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2a1cbb9cd3f47cfb7710cf50718e9987e2479d61b006e45a4d8fd005b963df93
size 73987
oid sha256:fa8c26e98e9cefb8e33902615f5b18c1931e0816d4a047715ef6cad2229b10dc
size 72245
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:219cb8f4e6eb3231b70758b3e749ea035ef46178520e7133659d378f8399f153
size 59974
oid sha256:33e4ea9bc6269d6ac5ce7ff6a76cdd1011e0f3dff96b920714b7f97d1aa1bf12
size 62540
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:71085b7d41122d58311cfac1b0660d36b76b364253e0a5053456ad4a1a91c3dc
size 58788
oid sha256:4e0406e368a19e1bc445b70c3b14128ed7c8bc02bd0e5da9c53611714e627a05
size 61706
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:14b54f69d324aceef11b754a3b66def948a22697cda3e7584303b3a9e45c536f
size 64169
oid sha256:7db669adacaceb7521c6c29a6ffc4b5a96bcfe6de12b36ca37bb944a76999048
size 70989
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:af9848cd44308df46cf42f3ed812f26e61740d52500b66b979cc03c378110524
size 56130
oid sha256:f241e93ac7db5568ec81fbfc23d2aaa8d1a6defa3280f3340b845ed7b554704d
size 57897
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:286b10d2c7cdaaf08373479de879b72dc674d4de1ecb62976ec6fcc53dc72289
size 62825
oid sha256:feb4a95d94bd3eb666a75ab9abbd314a2aaffbabf58ef6e48e400ad9fab1b147
size 69123
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c064561c817ee9b305fe9064d976aa80f4047f8ea5e0cf74f0b186d370f2edaa
size 54045
oid sha256:e73307fb019a9ca043ece75bd5f34e0198e86e5caf3a82c2e1517e2d121ffda5
size 55756
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e7d9d8fed9ba46489e2e079fd8999bd0b4716192c68f4be9a109b998ee5c2f15
size 11185
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:32284987439f0a1dd25daddc666348b48eb842ccf3c58d3be22f4c7973bc507f
size 11014
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:aa84713e216a9f9f153470d94886024b3ddead8aa0ccb5ca851cbac590ee2e6d
size 10341
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dd517f2b15ead6246fc42538a85663d7339c3d441e562b71be6016a9c2debe8f
size 10207