Merge pull request #5197 from element-hq/feature/bma/spaceUiComponent
Space UI component
This commit is contained in:
commit
30dff6a76f
69 changed files with 537 additions and 25 deletions
|
|
@ -46,7 +46,7 @@ import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewTitleAt
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonRowMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonRowMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.RoomPreviewMembersCountMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.MembersCountMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.organisms.RoomPreviewOrganism
|
import io.element.android.libraries.designsystem.atomic.organisms.RoomPreviewOrganism
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
import io.element.android.libraries.designsystem.components.Announcement
|
import io.element.android.libraries.designsystem.components.Announcement
|
||||||
|
|
@ -546,7 +546,7 @@ private fun DefaultLoadedContent(
|
||||||
},
|
},
|
||||||
memberCount = {
|
memberCount = {
|
||||||
if (contentState.showMemberCount) {
|
if (contentState.showMemberCount) {
|
||||||
RoomPreviewMembersCountMolecule(memberCount = contentState.numberOfMembers ?: 0)
|
MembersCountMolecule(memberCount = contentState.numberOfMembers ?: 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import io.element.android.libraries.designsystem.components.async.AsyncIndicator
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
|
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
|
||||||
import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState
|
import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState
|
||||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarRow
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RoomPreviewMembersCountMolecule(
|
fun MembersCountMolecule(
|
||||||
memberCount: Long,
|
memberCount: Long,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -51,13 +51,13 @@ fun RoomPreviewMembersCountMolecule(
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun RoomPreviewMembersCountMoleculePreview() = ElementPreview {
|
internal fun MembersCountMoleculePreview() = ElementPreview {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(8.dp),
|
modifier = Modifier.padding(8.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
RoomPreviewMembersCountMolecule(memberCount = 1)
|
MembersCountMolecule(memberCount = 1)
|
||||||
RoomPreviewMembersCountMolecule(memberCount = 888)
|
MembersCountMolecule(memberCount = 888)
|
||||||
RoomPreviewMembersCountMolecule(memberCount = 123_456)
|
MembersCountMolecule(memberCount = 123_456)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -16,8 +16,8 @@ open class AvatarDataProvider : PreviewParameterProvider<AvatarData> {
|
||||||
.map {
|
.map {
|
||||||
sequenceOf(
|
sequenceOf(
|
||||||
anAvatarData(size = it),
|
anAvatarData(size = it),
|
||||||
anAvatarData(size = it).copy(name = null),
|
anAvatarData(size = it, name = null),
|
||||||
anAvatarData(size = it).copy(url = "aUrl"),
|
anAvatarData(size = it, url = "aUrl"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|
@ -26,10 +26,12 @@ open class AvatarDataProvider : PreviewParameterProvider<AvatarData> {
|
||||||
fun anAvatarData(
|
fun anAvatarData(
|
||||||
// Let's the id not start with a 'a'.
|
// Let's the id not start with a 'a'.
|
||||||
id: String = "@id_of_alice:server.org",
|
id: String = "@id_of_alice:server.org",
|
||||||
name: String = "Alice",
|
name: String? = "Alice",
|
||||||
|
url: String? = null,
|
||||||
size: AvatarSize = AvatarSize.RoomListItem,
|
size: AvatarSize = AvatarSize.RoomListItem,
|
||||||
) = AvatarData(
|
) = AvatarData(
|
||||||
id = id,
|
id = id,
|
||||||
name = name,
|
name = name,
|
||||||
|
url = url,
|
||||||
size = size,
|
size = size,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.knockrequests.impl.banner
|
package io.element.android.libraries.designsystem.components.avatar
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
|
@ -23,10 +23,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
import io.element.android.libraries.designsystem.components.avatar.internal.OverlapRatioProvider
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.text.toPx
|
import io.element.android.libraries.designsystem.text.toPx
|
||||||
|
|
@ -41,6 +38,7 @@ import kotlinx.collections.immutable.toImmutableList
|
||||||
* @param modifier Jetpack Compose modifier
|
* @param modifier Jetpack Compose modifier
|
||||||
* @param overlapRatio the overlap ration. When 0f, avatars will render without overlap, when 1f
|
* @param overlapRatio the overlap ration. When 0f, avatars will render without overlap, when 1f
|
||||||
* only the first avatar will be visible
|
* only the first avatar will be visible
|
||||||
|
* @param lastOnTop if true, the last visible avatar will be rendered on top.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun AvatarRow(
|
fun AvatarRow(
|
||||||
|
|
@ -48,6 +46,7 @@ fun AvatarRow(
|
||||||
avatarType: AvatarType,
|
avatarType: AvatarType,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
overlapRatio: Float = 0.5f,
|
overlapRatio: Float = 0.5f,
|
||||||
|
lastOnTop: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
|
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -57,23 +56,35 @@ fun AvatarRow(
|
||||||
val avatarSize = avatarDataList.firstOrNull()?.size?.dp ?: return
|
val avatarSize = avatarDataList.firstOrNull()?.size?.dp ?: return
|
||||||
val avatarSizePx = avatarSize.toPx()
|
val avatarSizePx = avatarSize.toPx()
|
||||||
avatarDataList
|
avatarDataList
|
||||||
.reversed()
|
.let {
|
||||||
|
if (lastOnTop) {
|
||||||
|
it
|
||||||
|
} else {
|
||||||
|
it.reversed()
|
||||||
|
}
|
||||||
|
}
|
||||||
.forEachIndexed { index, avatarData ->
|
.forEachIndexed { index, avatarData ->
|
||||||
|
val startPadding = if (lastOnTop) {
|
||||||
|
avatarSize * (1 - overlapRatio) * index
|
||||||
|
} else {
|
||||||
|
avatarSize * (1 - overlapRatio) * (lastItemIndex - index)
|
||||||
|
}
|
||||||
Avatar(
|
Avatar(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = avatarSize * (1 - overlapRatio) * (lastItemIndex - index))
|
.padding(start = startPadding)
|
||||||
.graphicsLayer {
|
.graphicsLayer {
|
||||||
compositingStrategy = CompositingStrategy.Offscreen
|
compositingStrategy = CompositingStrategy.Offscreen
|
||||||
}
|
}
|
||||||
.drawWithContent {
|
.drawWithContent {
|
||||||
// Draw content and clear the pixels for the avatar on the left (right in RTL).
|
// Draw content and clear the pixels for the avatar on the left (right in RTL) or when lastOnTop is true on
|
||||||
|
// the right (left in RTL).
|
||||||
drawContent()
|
drawContent()
|
||||||
val xOffset = if (isRtl) {
|
|
||||||
size.width - avatarSizePx * (overlapRatio - 0.5f)
|
|
||||||
} else {
|
|
||||||
0f + avatarSizePx * (overlapRatio - 0.5f)
|
|
||||||
}
|
|
||||||
if (index < lastItemIndex) {
|
if (index < lastItemIndex) {
|
||||||
|
val xOffset = if (isRtl == lastOnTop) {
|
||||||
|
avatarSizePx * (overlapRatio - 0.5f)
|
||||||
|
} else {
|
||||||
|
size.width - avatarSizePx * (overlapRatio - 0.5f)
|
||||||
|
}
|
||||||
drawCircle(
|
drawCircle(
|
||||||
color = Color.Black,
|
color = Color.Black,
|
||||||
center = Offset(
|
center = Offset(
|
||||||
|
|
@ -104,6 +115,17 @@ internal fun AvatarRowPreview(@PreviewParameter(OverlapRatioProvider::class) ove
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@PreviewsDayNight
|
||||||
|
internal fun AvatarRowLastOnTopPreview(@PreviewParameter(OverlapRatioProvider::class) overlapRatio: Float) {
|
||||||
|
ElementPreview {
|
||||||
|
ContentToPreview(
|
||||||
|
overlapRatio = overlapRatio,
|
||||||
|
lastOnTop = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
internal fun AvatarRowRtlPreview(@PreviewParameter(OverlapRatioProvider::class) overlapRatio: Float) {
|
internal fun AvatarRowRtlPreview(@PreviewParameter(OverlapRatioProvider::class) overlapRatio: Float) {
|
||||||
|
|
@ -117,7 +139,25 @@ internal fun AvatarRowRtlPreview(@PreviewParameter(OverlapRatioProvider::class)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContentToPreview(overlapRatio: Float) {
|
@PreviewsDayNight
|
||||||
|
internal fun AvatarRowLastOnTopRtlPreview(@PreviewParameter(OverlapRatioProvider::class) overlapRatio: Float) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalLayoutDirection provides LayoutDirection.Rtl,
|
||||||
|
) {
|
||||||
|
ElementPreview {
|
||||||
|
ContentToPreview(
|
||||||
|
overlapRatio = overlapRatio,
|
||||||
|
lastOnTop = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ContentToPreview(
|
||||||
|
overlapRatio: Float,
|
||||||
|
lastOnTop: Boolean = false,
|
||||||
|
) {
|
||||||
AvatarRow(
|
AvatarRow(
|
||||||
avatarDataList = listOf("A", "B", "C").map {
|
avatarDataList = listOf("A", "B", "C").map {
|
||||||
AvatarData(
|
AvatarData(
|
||||||
|
|
@ -128,5 +168,6 @@ private fun ContentToPreview(overlapRatio: Float) {
|
||||||
}.toImmutableList(),
|
}.toImmutableList(),
|
||||||
avatarType = AvatarType.User,
|
avatarType = AvatarType.User,
|
||||||
overlapRatio = overlapRatio,
|
overlapRatio = overlapRatio,
|
||||||
|
lastOnTop = lastOnTop,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -63,4 +63,7 @@ enum class AvatarSize(val dp: Dp) {
|
||||||
DmCreationConfirmation(64.dp),
|
DmCreationConfirmation(64.dp),
|
||||||
|
|
||||||
UserVerification(52.dp),
|
UserVerification(52.dp),
|
||||||
|
|
||||||
|
OrganizationHeader(64.dp),
|
||||||
|
SpaceMember(24.dp),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.features.knockrequests.impl.banner
|
package io.element.android.libraries.designsystem.components.avatar.internal
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector 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.matrix.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.ripple
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.BlendMode
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.CompositingStrategy
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||||
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
|
import androidx.compose.ui.semantics.onClick
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
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.components.avatar.Avatar
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.text.toPx
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=3643-2678&m=dev
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun EditableOrgAvatar(
|
||||||
|
avatarData: AvatarData,
|
||||||
|
onEdit: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val actionEdit = stringResource(id = CommonStrings.action_edit)
|
||||||
|
val description = stringResource(CommonStrings.a11y_avatar)
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.width(avatarData.size.dp + 16.dp)
|
||||||
|
.clearAndSetSemantics {
|
||||||
|
contentDescription = description
|
||||||
|
// Note: this does not set the click effect to the whole Box
|
||||||
|
// when talkback is not enabled
|
||||||
|
onClick(
|
||||||
|
label = actionEdit,
|
||||||
|
action = {
|
||||||
|
onEdit()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
|
||||||
|
val editIconRadius = 17.dp.toPx()
|
||||||
|
val editIconXOffset = 7.dp.toPx()
|
||||||
|
val editIconYOffset = 15.dp.toPx()
|
||||||
|
Avatar(
|
||||||
|
avatarData = avatarData,
|
||||||
|
avatarType = AvatarType.Space(false),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.graphicsLayer {
|
||||||
|
compositingStrategy = CompositingStrategy.Offscreen
|
||||||
|
}
|
||||||
|
.drawWithContent {
|
||||||
|
drawContent()
|
||||||
|
val xOffset = if (isRtl) {
|
||||||
|
editIconXOffset
|
||||||
|
} else {
|
||||||
|
size.width - editIconXOffset
|
||||||
|
}
|
||||||
|
drawCircle(
|
||||||
|
color = Color.Black,
|
||||||
|
center = Offset(
|
||||||
|
x = xOffset,
|
||||||
|
y = size.height - editIconYOffset,
|
||||||
|
),
|
||||||
|
radius = editIconRadius,
|
||||||
|
blendMode = BlendMode.Clear,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Surface(
|
||||||
|
color = ElementTheme.colors.bgCanvasDefault,
|
||||||
|
shape = CircleShape,
|
||||||
|
border = BorderStroke(1.dp, color = ElementTheme.colors.borderInteractiveSecondary),
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.size(30.dp)
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.clickable(
|
||||||
|
indication = ripple(),
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onClick = onEdit,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = CompoundIcons.Edit(),
|
||||||
|
// Note: keep the context description for the test
|
||||||
|
contentDescription = stringResource(id = CommonStrings.action_edit),
|
||||||
|
tint = ElementTheme.colors.iconPrimary,
|
||||||
|
modifier = Modifier.padding(6.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun EditableOrgAvatarPreview() = ElementPreview {
|
||||||
|
EditableOrgAvatar(
|
||||||
|
avatarData = anAvatarData(
|
||||||
|
url = "anUrl",
|
||||||
|
size = AvatarSize.OrganizationHeader,
|
||||||
|
),
|
||||||
|
onEdit = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun EditableOrgAvatarRtlPreview() = CompositionLocalProvider(
|
||||||
|
LocalLayoutDirection provides LayoutDirection.Rtl,
|
||||||
|
) {
|
||||||
|
ElementPreview {
|
||||||
|
EditableOrgAvatar(
|
||||||
|
avatarData = anAvatarData(
|
||||||
|
url = "anUrl",
|
||||||
|
size = AvatarSize.OrganizationHeader,
|
||||||
|
),
|
||||||
|
onEdit = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector 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.matrix.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.ui.strings.CommonPlurals
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=3643-2048&m=dev
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun OrganizationHeader(
|
||||||
|
avatarData: AvatarData,
|
||||||
|
name: String,
|
||||||
|
numberOfSpaces: Int,
|
||||||
|
numberOfRooms: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 16.dp, bottom = 24.dp, start = 16.dp, end = 16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Avatar(
|
||||||
|
avatarData = avatarData,
|
||||||
|
avatarType = AvatarType.Space(false),
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Text(
|
||||||
|
text = name,
|
||||||
|
style = ElementTheme.typography.fontHeadingLgBold,
|
||||||
|
color = ElementTheme.colors.textPrimary,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
val subtitle = stringResource(
|
||||||
|
id = CommonStrings.screen_space_list_details,
|
||||||
|
pluralStringResource(CommonPlurals.common_spaces, numberOfSpaces, numberOfSpaces),
|
||||||
|
pluralStringResource(CommonPlurals.common_rooms, numberOfRooms, numberOfRooms),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = subtitle,
|
||||||
|
style = ElementTheme.typography.fontBodyLgRegular,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun OrganizationHeaderPreview() = ElementPreview {
|
||||||
|
OrganizationHeader(
|
||||||
|
avatarData = anAvatarData(
|
||||||
|
url = "anUrl",
|
||||||
|
size = AvatarSize.OrganizationHeader,
|
||||||
|
),
|
||||||
|
name = "Space name",
|
||||||
|
numberOfSpaces = 9,
|
||||||
|
numberOfRooms = 88,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector 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.matrix.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.libraries.designsystem.atomic.molecules.MembersCountMolecule
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarRow
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=3729-605&m=dev
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun SpaceMembersView(
|
||||||
|
heroes: ImmutableList<MatrixUser>,
|
||||||
|
numberOfMembers: Long,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
if (heroes.isEmpty()) {
|
||||||
|
MembersCountMolecule(
|
||||||
|
memberCount = numberOfMembers,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
SpaceMembersWithAvatar(
|
||||||
|
heroes = heroes
|
||||||
|
.take(3)
|
||||||
|
.map {
|
||||||
|
it.getAvatarData(AvatarSize.SpaceMember)
|
||||||
|
}
|
||||||
|
.toImmutableList(),
|
||||||
|
numberOfMembers = numberOfMembers,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SpaceMembersWithAvatar(
|
||||||
|
heroes: ImmutableList<AvatarData>,
|
||||||
|
numberOfMembers: Long,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
AvatarRow(
|
||||||
|
avatarDataList = heroes,
|
||||||
|
avatarType = AvatarType.User,
|
||||||
|
lastOnTop = true,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "$numberOfMembers",
|
||||||
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@PreviewsDayNight
|
||||||
|
internal fun SpaceMembersViewNoHeroesPreview() = ElementPreview {
|
||||||
|
SpaceMembersView(
|
||||||
|
heroes = persistentListOf(),
|
||||||
|
numberOfMembers = 123,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@PreviewsDayNight
|
||||||
|
internal fun SpaceMembersViewPreview() = ElementPreview(
|
||||||
|
drawableFallbackForImages = CommonDrawables.sample_avatar,
|
||||||
|
) {
|
||||||
|
SpaceMembersView(
|
||||||
|
heroes = persistentListOf(
|
||||||
|
aMatrixUser(id = "@1:d", displayName = "Alice", avatarUrl = "aUrl"),
|
||||||
|
aMatrixUser(id = "@2:d", displayName = "Bob"),
|
||||||
|
aMatrixUser(id = "@3:d", displayName = "Charlie", avatarUrl = "aUrl"),
|
||||||
|
aMatrixUser(id = "@4:d", displayName = "Dave"),
|
||||||
|
),
|
||||||
|
numberOfMembers = 123,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -98,6 +98,7 @@ class KonsistPreviewTest {
|
||||||
"SasEmojisPreview",
|
"SasEmojisPreview",
|
||||||
"SecureBackupSetupViewChangePreview",
|
"SecureBackupSetupViewChangePreview",
|
||||||
"SelectedUserCannotRemovePreview",
|
"SelectedUserCannotRemovePreview",
|
||||||
|
"SpaceMembersViewNoHeroesPreview",
|
||||||
"TextComposerAddCaptionPreview",
|
"TextComposerAddCaptionPreview",
|
||||||
"TextComposerCaptionPreview",
|
"TextComposerCaptionPreview",
|
||||||
"TextComposerEditCaptionPreview",
|
"TextComposerEditCaptionPreview",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:d510dce684941c35ef1b6ea670156e2e5d5ed0eee3577c433e02ca073e0e84c5
|
||||||
|
size 8615
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:577579f2ed5d7ffb0d289f91d98b13b1df2f9846fefd9c494522809b18de1854
|
||||||
|
size 8225
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:f053948660fad8b021a4b22d9cd234cde1cce72c3165c812000bd97b0c2da440
|
||||||
|
size 7156
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:66c4f53b8a2b81285243a162c56521d2cb011ece4e725b9c2152d498b9f5a382
|
||||||
|
size 5563
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:400a14cd9e24e55351cace9c44432d94d5a81b827a19dff8e61154193e13393a
|
||||||
|
size 8879
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:31a781b781a1dccbf15012c2171f0467e54675c3b6c79740b63b9dc878324754
|
||||||
|
size 8500
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:65cd68f9efcac21733ad327afa3308c09b8e1eece9c0d4323e7c7261a4cd3370
|
||||||
|
size 7502
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a9f18b956d5280c31a4fae4754ddd8c2300e37580dd477b06ed043e198b08fa3
|
||||||
|
size 5655
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:cf2ad9276a0aed1b9a97067a4ce60938b0fd556050db36cf28631ce0d69a8c36
|
||||||
|
size 8588
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:223be8c6d34b8a19758575c7798ca6d925efb391c1ceabd0a1ed4c472364dcbb
|
||||||
|
size 7823
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:b6e439c9cd3b611cb8beac629d4d6e4da8927d05199aefec9eabe3e77ee67fe4
|
||||||
|
size 7187
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:66c4f53b8a2b81285243a162c56521d2cb011ece4e725b9c2152d498b9f5a382
|
||||||
|
size 5563
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:fb3cec5cf746f7faf350c3bc8cb919e5ba0e207ff87959e817e86d82e8531d72
|
||||||
|
size 8831
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:489cf3807e915dff5ff19458166ab12379002edc42f745e28ae40d151316ed72
|
||||||
|
size 8143
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:dc8bd17e8101352a0bdf542b1fe2ef0de85aa44f78afeb582d7ee18af4d9af75
|
||||||
|
size 7491
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a9f18b956d5280c31a4fae4754ddd8c2300e37580dd477b06ed043e198b08fa3
|
||||||
|
size 5655
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:781862888b73f9bb0948758783a3987d2e04ac2e0690996cdc913b09e4d7283c
|
||||||
|
size 8645
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:21edc3161193318e2b1ca0a6b1136b76616011b3eb3bf0a3308f81b9c9038303
|
||||||
|
size 8957
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:7dc701122587804c0e8fe5e238dbfdddd0c93cb3a15c50eab2b0439a8b0a8f86
|
||||||
|
size 8638
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:25262fd5004a941229d6404d73cbacb8c10b4c009b519acb07584eada553bf22
|
||||||
|
size 8946
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:90982bf0b723b89c9b070c1dc94eb5a9fcf66623c382d496174f026e053929fb
|
||||||
|
size 19493
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:9640e8e6d758a03f995c7fbbb6c3c123109594138a7cc53d5416ef5e3e157693
|
||||||
|
size 18006
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:fbe699fb17947981c53d47a9570da71e2388ec493a3a9471aaa13a5405075069
|
||||||
|
size 23421
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ce1aba18ea8a6b45d9c40de9af961952e261c7c6200ab13cdfa67906d995208b
|
||||||
|
size 14888
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6a4d295ae71ba2709845312b84983b79348069ded46b30091b9fa769a587cbb7
|
||||||
|
size 14337
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:da8f54591a52475c6f47fa083a1bb397e1164dd3e735562388fde4ff45644150
|
||||||
|
size 16275
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:2b9bd735000eda1054294863bdff79d32b391e1511f025bdc05ba4374be3f27c
|
||||||
|
size 31769
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6e9f6f4fe633ca50c2a27cc2e2a0157880f698f91cf759a35fa3f4d49b805905
|
||||||
|
size 31332
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:9a7773c27cc488967a4c8c526f4d19ca3a78e15c1efa2f7267881c2e9798d5c4
|
||||||
|
size 31223
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:3f837b094f91617dba15cbaab3336aa501b0a834299a07fc6673c9b6b89b8905
|
||||||
|
size 30702
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:862a2a93700999b27dbdf461e70d2e97441550795d03ccb3e4ec4f964d3d6d99
|
||||||
|
size 41995
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:33f802e03011231b8ef81574b12e9622cf9d3901e3faf0225b8e745e88f50623
|
||||||
|
size 41324
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:9df673a4073b6806332b3181987f7ff0594eca818ef4a2b760136582d16de476
|
||||||
|
size 6191
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:c6a4bf38d356974df822f69f6de85b5b83b4622395c98b1c72d2d352b4e820ce
|
||||||
|
size 6093
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:0df7c79b75dabc28fbae243d83547bb3786cb36eca0d33cabc38db0c40ffeb90
|
||||||
|
size 6993
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:f1c7cd3392fa7550edf07859f3e5c9a8e69c3e20ea784ac5acffcee5876a4dca
|
||||||
|
size 7312
|
||||||
Loading…
Add table
Add a link
Reference in a new issue