From d8b1a41f560a40ffec47e5998f99d22ea558b229 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 2 Jul 2026 14:45:22 +0200 Subject: [PATCH] Create dedicated package for visual list and add Figma link. --- .../analytics/impl/AnalyticsOptInView.kt | 18 ++++----- .../fullscreen/FullscreenAnnouncementView.kt | 18 ++++----- .../logout/impl/AccountDeactivationView.kt | 14 +++---- .../impl/reset/root/ResetIdentityRootView.kt | 12 +++--- .../features/signedout/impl/SignedOutView.kt | 18 ++++----- .../visuallist/VisualList.kt} | 40 ++++++++----------- .../visuallist/VisualListItem.kt} | 37 +++++++++-------- .../visuallist/VisualListItemData.kt | 19 +++++++++ 8 files changed, 95 insertions(+), 81 deletions(-) rename libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/{atomic/organisms/InfoListOrganism.kt => components/visuallist/VisualList.kt} (75%) rename libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/{atomic/molecules/InfoListItemMolecule.kt => components/visuallist/VisualListItem.kt} (74%) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/visuallist/VisualListItemData.kt diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt index 4fc9ce5c6b..80071241ba 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt @@ -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 @@ -65,9 +65,9 @@ fun AnalyticsOptInView( BackHandler(onBack = ::onDeclineTerms) HeaderFooterPage( modifier = modifier - .fillMaxSize() - .systemBarsPadding() - .imePadding(), + .fillMaxSize() + .systemBarsPadding() + .imePadding(), background = { OnboardingBackground() }, header = { AnalyticsOptInHeader(state, onClickTerms) }, content = { AnalyticsOptInContent() }, @@ -129,17 +129,17 @@ 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(), ), diff --git a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/fullscreen/FullscreenAnnouncementView.kt b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/fullscreen/FullscreenAnnouncementView.kt index c544fd4914..d396f1b8b6 100644 --- a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/fullscreen/FullscreenAnnouncementView.kt +++ b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/fullscreen/FullscreenAnnouncementView.kt @@ -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,7 +134,7 @@ private fun FullscreenAnnouncementContent( Column( modifier = modifier.fillMaxSize(), ) { - InfoListOrganism( + VisualList( modifier = Modifier.fillMaxWidth(), items = announcement.items(), textStyle = ElementTheme.typography.fontBodyLgMedium, @@ -186,25 +186,25 @@ private fun Announcement.Fullscreen.icon() = when (this) { } @Composable -private fun Announcement.Fullscreen.items(): ImmutableList = when (this) { +private fun Announcement.Fullscreen.items(): ImmutableList = 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(), ), diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationView.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationView.kt index c942d2df72..738046b3a3 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationView.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationView.kt @@ -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( diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootView.kt index 97c82f446d..7ac8dbe4cf 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootView.kt @@ -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( diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt index f89b36e4d4..8268e127f6 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt @@ -25,10 +25,10 @@ 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 @@ -43,9 +43,9 @@ fun SignedOutView( BackHandler(onBack = { state.eventSink(SignedOutEvents.SignInAgain) }) HeaderFooterPage( modifier = modifier - .fillMaxSize() - .systemBarsPadding() - .imePadding(), + .fillMaxSize() + .systemBarsPadding() + .imePadding(), header = { SignedOutHeader(state) }, content = { SignedOutContent() }, footer = { @@ -75,17 +75,17 @@ 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(), ), diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/InfoListOrganism.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/visuallist/VisualList.kt similarity index 75% rename from libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/InfoListOrganism.kt rename to libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/visuallist/VisualList.kt index f52ee2512f..65141df014 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/InfoListOrganism.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/visuallist/VisualList.kt @@ -6,9 +6,8 @@ * 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 @@ -17,14 +16,11 @@ 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,9 +28,12 @@ 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, +fun VisualList( + items: ImmutableList, modifier: Modifier = Modifier, backgroundColor: Color = ElementTheme.colors.bgSubtleSecondary, iconTint: Color = LocalContentColor.current, @@ -49,12 +48,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 +95,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, ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InfoListItemMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/visuallist/VisualListItem.kt similarity index 74% rename from libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InfoListItemMolecule.kt rename to libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/visuallist/VisualListItem.kt index 46d8029c89..276fb1522e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InfoListItemMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/visuallist/VisualListItem.kt @@ -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, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/visuallist/VisualListItemData.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/visuallist/VisualListItemData.kt new file mode 100644 index 0000000000..ace971ac61 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/visuallist/VisualListItemData.kt @@ -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 = {}, +)