From f4325aed503c5b2f08b7ab3fe99a8b0adcc376b4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 21 Jun 2024 15:43:27 +0200 Subject: [PATCH] Create DmAvatars to render both users of a DM. --- .../components/avatar/AvatarSize.kt | 2 + .../components/avatar/DmAvatars.kt | 115 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/DmAvatars.kt diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index e56c580abb..adf87af45a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -36,6 +36,8 @@ enum class AvatarSize(val dp: Dp) { SelectedUser(56.dp), SelectedRoom(56.dp), + DmCluster(75.dp), + TimelineRoom(32.dp), TimelineSender(32.dp), TimelineReadReceipt(16.dp), diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/DmAvatars.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/DmAvatars.kt new file mode 100644 index 0000000000..a45fbde6e3 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/DmAvatars.kt @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components.avatar + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +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.tooling.preview.Preview +import io.element.android.libraries.designsystem.preview.ElementThemedPreview +import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.text.toPx + +/** + * https://www.figma.com/design/A2pAEvTEpJZBiOPUlcMnKi/Settings-%2B-Room-Details-(new)?node-id=1787-56333 + */ + +/** Ratio between the box size (120 on Figma) and the avatar size (75 on Figma). */ +private const val SIZE_RATIO = 1.6f + +@Composable +fun DmAvatars( + userAvatarData: AvatarData, + otherUserAvatarData: AvatarData, + openAvatarPreview: (url: String) -> Unit, + openOtherAvatarPreview: (url: String) -> Unit, + modifier: Modifier = Modifier, +) { + val boxSize = userAvatarData.size.dp * SIZE_RATIO + val boxSizePx = boxSize.toPx() + val otherAvatarRadius = otherUserAvatarData.size.dp.toPx() / 2 + Box( + modifier = modifier.size(boxSize), + ) { + // Draw user avatar and cut top right corner + Avatar( + avatarData = userAvatarData, + modifier = Modifier + .align(Alignment.BottomStart) + .graphicsLayer { + compositingStrategy = CompositingStrategy.Offscreen + } + .drawWithContent { + drawContent() + drawCircle( + color = Color.Black, + center = Offset( + x = boxSizePx - otherAvatarRadius, + y = size.height - (boxSizePx - otherAvatarRadius), + ), + radius = otherAvatarRadius / 0.9f, + blendMode = BlendMode.Clear, + ) + } + .clip(CircleShape) + .clickable(enabled = userAvatarData.url != null) { + userAvatarData.url?.let { openAvatarPreview(it) } + } + ) + // Draw other user avatar + Avatar( + avatarData = otherUserAvatarData, + modifier = Modifier + .align(Alignment.TopEnd) + .clip(CircleShape) + .clickable(enabled = otherUserAvatarData.url != null) { + otherUserAvatarData.url?.let { openOtherAvatarPreview(it) } + } + ) + } +} + +@Preview(group = PreviewGroup.Avatars) +@Composable +internal fun DmAvatarsPreview() = ElementThemedPreview { + val size = AvatarSize.DmCluster + DmAvatars( + userAvatarData = anAvatarData( + id = "Alice", + name = "Alice", + size = size, + ), + otherUserAvatarData = anAvatarData( + id = "Bob", + name = "Bob", + size = size, + ), + openAvatarPreview = {}, + openOtherAvatarPreview = {}, + ) +}