From 896123979d47496bb5d112e0a5ecd371680eecb2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 31 Dec 2024 14:48:05 +0100 Subject: [PATCH 1/9] Add preview for DmAvatars and Rtl layout. --- .../designsystem/components/avatar/DmAvatars.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 index 3b8c78a619..7b92e980c0 100644 --- 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 @@ -12,6 +12,7 @@ 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.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -21,7 +22,9 @@ 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.tooling.preview.Preview +import androidx.compose.ui.unit.LayoutDirection import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.text.toPx @@ -106,3 +109,13 @@ internal fun DmAvatarsPreview() = ElementThemedPreview { openOtherAvatarPreview = {}, ) } + +@Preview(group = PreviewGroup.Avatars) +@Composable +internal fun DmAvatarsRtlPreview() { + CompositionLocalProvider( + LocalLayoutDirection provides LayoutDirection.Rtl, + ) { + DmAvatarsPreview() + } +} From 37cd9efc34d34382bad45f9160febced43c2dd72 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 31 Dec 2024 15:09:35 +0100 Subject: [PATCH 2/9] Fix DmAvatar rendering in RTL layout --- .../designsystem/components/avatar/DmAvatars.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 index 7b92e980c0..e9073f89fd 100644 --- 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 @@ -48,10 +48,11 @@ fun DmAvatars( val boxSize = userAvatarData.size.dp * SIZE_RATIO val boxSizePx = boxSize.toPx() val otherAvatarRadius = otherUserAvatarData.size.dp.toPx() / 2 + val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl Box( modifier = modifier.size(boxSize), ) { - // Draw user avatar and cut top right corner + // Draw user avatar and cut top end corner Avatar( avatarData = userAvatarData, modifier = Modifier @@ -61,10 +62,15 @@ fun DmAvatars( } .drawWithContent { drawContent() + val xOffset = if (isRtl) { + size.width - boxSizePx + otherAvatarRadius + } else { + boxSizePx - otherAvatarRadius + } drawCircle( color = Color.Black, center = Offset( - x = boxSizePx - otherAvatarRadius, + x = xOffset, y = size.height - (boxSizePx - otherAvatarRadius), ), radius = otherAvatarRadius / 0.9f, From 45f29e015f6d9fa6cb105da119387773848601a7 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 31 Dec 2024 14:25:31 +0000 Subject: [PATCH 3/9] Update screenshots --- ....designsystem.components.avatar_DmAvatarsRtl_Avatars_en.png | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_DmAvatarsRtl_Avatars_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_DmAvatarsRtl_Avatars_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_DmAvatarsRtl_Avatars_en.png new file mode 100644 index 0000000000..80aeb9dfb6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_DmAvatarsRtl_Avatars_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0007ee6df39ec6b8f180cab21a99f97adef39deb1055ba8d179e5fee5ca1203 +size 13856 From 3add7be8728b924e8ef79f76222055865c5291b2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 31 Dec 2024 17:33:29 +0100 Subject: [PATCH 4/9] Extract AvatarRow to its own file --- .../knockrequests/impl/banner/AvatarRow.kt | 65 +++++++++++++++++++ .../impl/banner/KnockRequestsBannerView.kt | 58 ++++------------- 2 files changed, 76 insertions(+), 47 deletions(-) create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt new file mode 100644 index 0000000000..f532857280 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.banner + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +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.unit.dp +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarData + +@Composable +fun AvatarRow( + avatarDataList: List, + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier, + ) { + val lastItemIndex = avatarDataList.size - 1 + val avatarSize = avatarDataList.firstOrNull()?.size?.dp ?: return + avatarDataList + .reversed() + .forEachIndexed { index, avatarData -> + Avatar( + modifier = Modifier + .padding(start = avatarSize / 2 * (lastItemIndex - index)) + .graphicsLayer { + compositingStrategy = CompositingStrategy.Offscreen + } + .drawWithContent { + // Draw content and clear the pixels for the avatar on the left. + drawContent() + if (index < lastItemIndex) { + drawCircle( + color = Color.Black, + center = Offset( + x = 0f, + y = size.height / 2, + ), + radius = avatarSize.toPx() / 2, + blendMode = BlendMode.Clear, + ) + } + } + .size(size = avatarSize) + .padding(2.dp), + avatarData = avatarData, + ) + } + } +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index d029b80906..a7307d605b 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -19,19 +19,12 @@ 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.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier -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.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -120,9 +113,9 @@ private fun KnockRequestsBannerContent( } Column( - modifier - .fillMaxWidth() - .padding(all = 16.dp) + modifier + .fillMaxWidth() + .padding(all = 16.dp) ) { Row { KnockRequestAvatarView( @@ -212,44 +205,15 @@ private fun KnockRequestAvatarListView( knockRequests: ImmutableList, modifier: Modifier = Modifier, ) { - val avatarSize = AvatarSize.KnockRequestBanner.dp - Box( + val avatars = knockRequests + .take(MAX_AVATAR_COUNT) + .map { knockRequest -> + knockRequest.getAvatarData(AvatarSize.KnockRequestBanner) + } + AvatarRow( + avatarDataList = avatars, modifier = modifier, - ) { - knockRequests - .take(MAX_AVATAR_COUNT) - .reversed() - .let { smallReversedList -> - val lastItemIndex = smallReversedList.size - 1 - smallReversedList.forEachIndexed { index, knockRequest -> - Avatar( - modifier = Modifier - .padding(start = avatarSize / 2 * (lastItemIndex - index)) - .graphicsLayer { - compositingStrategy = CompositingStrategy.Offscreen - } - .drawWithContent { - // Draw content and clear the pixels for the avatar on the left. - drawContent() - if (index < lastItemIndex) { - drawCircle( - color = Color.Black, - center = Offset( - x = 0f, - y = size.height / 2, - ), - radius = avatarSize.toPx() / 2, - blendMode = BlendMode.Clear, - ) - } - } - .size(size = avatarSize) - .padding(2.dp), - avatarData = knockRequest.getAvatarData(AvatarSize.KnockRequestBanner), - ) - } - } - } + ) } @Composable From cd93ab9cd2975b87822397a81b9d0925e4ab99d4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 31 Dec 2024 17:36:41 +0100 Subject: [PATCH 5/9] Add preview on AvatarRow --- .../knockrequests/impl/banner/AvatarRow.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt index f532857280..5cd728d443 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt @@ -21,6 +21,9 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp 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.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight @Composable fun AvatarRow( @@ -63,3 +66,19 @@ fun AvatarRow( } } } + +@Composable +@PreviewsDayNight +internal fun AvatarRowPreview() = ElementPreview { + AvatarRow( + avatarDataList = listOf( + "A", "B", "C" + ).map { + AvatarData( + id = it, + name = it, + size = AvatarSize.RoomListItem, + ) + } + ) +} From 9f6f812377cb471325a2a9d54bd19a163468bbf5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 2 Jan 2025 06:15:00 +0100 Subject: [PATCH 6/9] Fix issue when rendering in RTL. --- .../knockrequests/impl/banner/AvatarRow.kt | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt index 5cd728d443..4fc31b192b 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset @@ -18,6 +19,8 @@ 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.unit.LayoutDirection import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData @@ -30,6 +33,7 @@ fun AvatarRow( avatarDataList: List, modifier: Modifier = Modifier, ) { + val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl Box( modifier = modifier, ) { @@ -45,13 +49,13 @@ fun AvatarRow( compositingStrategy = CompositingStrategy.Offscreen } .drawWithContent { - // Draw content and clear the pixels for the avatar on the left. + // Draw content and clear the pixels for the avatar on the left (right in RTL). drawContent() if (index < lastItemIndex) { drawCircle( color = Color.Black, center = Offset( - x = 0f, + x = if (isRtl) size.width else 0f, y = size.height / 2, ), radius = avatarSize.toPx() / 2, @@ -82,3 +86,13 @@ internal fun AvatarRowPreview() = ElementPreview { } ) } + +@Composable +@PreviewsDayNight +internal fun AvatarRowRtlPreview() { + CompositionLocalProvider( + LocalLayoutDirection provides LayoutDirection.Rtl, + ) { + AvatarRowPreview() + } +} From df108adced9931dad1ab0bd8e2991f5b4cea12cf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 2 Jan 2025 06:43:46 +0100 Subject: [PATCH 7/9] Add overlapRatio parameter --- .../knockrequests/impl/banner/AvatarRow.kt | 59 ++++++++++++++----- .../impl/banner/OverlapRatioProvider.kt | 20 +++++++ 2 files changed, 64 insertions(+), 15 deletions(-) create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/OverlapRatioProvider.kt diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt index 4fc31b192b..ee8d8000e2 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt @@ -20,6 +20,7 @@ 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.tooling.preview.PreviewParameter import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.components.avatar.Avatar @@ -27,11 +28,21 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.text.toPx +/** + * Draw a row of avatars (they must all have the same size), from start to end + * @param avatarDataList the avatars to render. Note: they will all be rendered, the caller may + * want to limit the list size + * @param modifier Jetpack Compose modifier + * @param overlapRatio the overlap ration. When 0f, avatars will render without overlap, when 1f + * only the first avatar will be visible + */ @Composable fun AvatarRow( avatarDataList: List, modifier: Modifier = Modifier, + overlapRatio: Float = 0.5f, ) { val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl Box( @@ -39,31 +50,39 @@ fun AvatarRow( ) { val lastItemIndex = avatarDataList.size - 1 val avatarSize = avatarDataList.firstOrNull()?.size?.dp ?: return + val avatarSizePx = avatarSize.toPx() avatarDataList .reversed() .forEachIndexed { index, avatarData -> Avatar( modifier = Modifier - .padding(start = avatarSize / 2 * (lastItemIndex - index)) + .padding(start = avatarSize * (1 - overlapRatio) * (lastItemIndex - index)) .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } .drawWithContent { // Draw content and clear the pixels for the avatar on the left (right in RTL). drawContent() + val xOffset = if (isRtl) { + size.width - avatarSizePx * (overlapRatio - 0.5f) + } else { + 0f + avatarSizePx * (overlapRatio - 0.5f) + } if (index < lastItemIndex) { drawCircle( color = Color.Black, center = Offset( - x = if (isRtl) size.width else 0f, + x = xOffset, y = size.height / 2, ), - radius = avatarSize.toPx() / 2, + radius = avatarSizePx / 2, blendMode = BlendMode.Clear, ) } } .size(size = avatarSize) + // Keep internal padding, it has the advantage to not reduce the size of the Avatar image, + // which is already small in our use case. .padding(2.dp), avatarData = avatarData, ) @@ -73,7 +92,26 @@ fun AvatarRow( @Composable @PreviewsDayNight -internal fun AvatarRowPreview() = ElementPreview { +internal fun AvatarRowPreview(@PreviewParameter(OverlapRatioProvider::class) overlapRatio: Float) { + ElementPreview { + ContentToPreview(overlapRatio) + } +} + +@Composable +@PreviewsDayNight +internal fun AvatarRowRtlPreview(@PreviewParameter(OverlapRatioProvider::class) overlapRatio: Float) { + CompositionLocalProvider( + LocalLayoutDirection provides LayoutDirection.Rtl, + ) { + ElementPreview { + ContentToPreview(overlapRatio) + } + } +} + +@Composable +private fun ContentToPreview(overlapRatio: Float) { AvatarRow( avatarDataList = listOf( "A", "B", "C" @@ -83,16 +121,7 @@ internal fun AvatarRowPreview() = ElementPreview { name = it, size = AvatarSize.RoomListItem, ) - } + }, + overlapRatio = overlapRatio, ) } - -@Composable -@PreviewsDayNight -internal fun AvatarRowRtlPreview() { - CompositionLocalProvider( - LocalLayoutDirection provides LayoutDirection.Rtl, - ) { - AvatarRowPreview() - } -} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/OverlapRatioProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/OverlapRatioProvider.kt new file mode 100644 index 0000000000..3725f3f251 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/OverlapRatioProvider.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.banner + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +class OverlapRatioProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf( + 0f, + 0.25f, + 0.5f, + 0.75f, + 1f + ) +} From b1cd02ff70ef980223dfc81f345f5a1994151dc4 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 2 Jan 2025 06:15:47 +0000 Subject: [PATCH 8/9] Update screenshots --- ...eatures.knockrequests.impl.banner_AvatarRowRtl_Day_0_en.png | 3 +++ ...eatures.knockrequests.impl.banner_AvatarRowRtl_Day_1_en.png | 3 +++ ...eatures.knockrequests.impl.banner_AvatarRowRtl_Day_2_en.png | 3 +++ ...eatures.knockrequests.impl.banner_AvatarRowRtl_Day_3_en.png | 3 +++ ...eatures.knockrequests.impl.banner_AvatarRowRtl_Day_4_en.png | 3 +++ ...tures.knockrequests.impl.banner_AvatarRowRtl_Night_0_en.png | 3 +++ ...tures.knockrequests.impl.banner_AvatarRowRtl_Night_1_en.png | 3 +++ ...tures.knockrequests.impl.banner_AvatarRowRtl_Night_2_en.png | 3 +++ ...tures.knockrequests.impl.banner_AvatarRowRtl_Night_3_en.png | 3 +++ ...tures.knockrequests.impl.banner_AvatarRowRtl_Night_4_en.png | 3 +++ .../features.knockrequests.impl.banner_AvatarRow_Day_0_en.png | 3 +++ .../features.knockrequests.impl.banner_AvatarRow_Day_1_en.png | 3 +++ .../features.knockrequests.impl.banner_AvatarRow_Day_2_en.png | 3 +++ .../features.knockrequests.impl.banner_AvatarRow_Day_3_en.png | 3 +++ .../features.knockrequests.impl.banner_AvatarRow_Day_4_en.png | 3 +++ ...features.knockrequests.impl.banner_AvatarRow_Night_0_en.png | 3 +++ ...features.knockrequests.impl.banner_AvatarRow_Night_1_en.png | 3 +++ ...features.knockrequests.impl.banner_AvatarRow_Night_2_en.png | 3 +++ ...features.knockrequests.impl.banner_AvatarRow_Night_3_en.png | 3 +++ ...features.knockrequests.impl.banner_AvatarRow_Night_4_en.png | 3 +++ 20 files changed, 60 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_4_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_0_en.png new file mode 100644 index 0000000000..114f691390 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d318e0e4fa1bd8a123057dca6efc9c92c5dc96614dad3514813559274288e115 +size 8520 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_1_en.png new file mode 100644 index 0000000000..3239e8119b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0bee0c2e2c712621bbc92c430807c758429ef1179394b107c393f9236295bbb +size 8447 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_2_en.png new file mode 100644 index 0000000000..b6897461c1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a92d34b2ac73b7d3341f39f46bbf0a4dcf9862e9cccdfc0111b64c562930f03 +size 7720 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_3_en.png new file mode 100644 index 0000000000..1d20118dc6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79a021e58c28d0e0cbb50f99304dae5c8c2ec95ca99a02d7876a167adc911e7f +size 6948 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_4_en.png new file mode 100644 index 0000000000..5883980ec4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea4febe911c963e720535c576a65842d2616ffb61103153a8a8738886c26eca7 +size 5251 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_0_en.png new file mode 100644 index 0000000000..6ee3a1513b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33ad034f7be86da7a2399d11c0991b5d6997ddaffa9f26b20a456f1b22666b70 +size 8825 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_1_en.png new file mode 100644 index 0000000000..ebf06ae029 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5784d917b0176cfa6b584804b28d8592ed97fe6e1f60a805619cce3170acb802 +size 8722 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_2_en.png new file mode 100644 index 0000000000..cd5166ea80 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06a0cef40417aa586e85b5896e763279e2c3df6121ffd65b9b85129e2a5ea4f4 +size 8023 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_3_en.png new file mode 100644 index 0000000000..ecb7dc66c1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac5300b143c12ebf39fb6b0074677b5f16760217cca3823229304838e322e424 +size 7375 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_4_en.png new file mode 100644 index 0000000000..ea48f4c556 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRowRtl_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:690a1313ac8dd2cb84d7d560a16bdcd3eb0b65ad16d502da37efccecc4198b27 +size 5467 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_0_en.png new file mode 100644 index 0000000000..91761c2db8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:836e6d27c46a85d5a8d6fd8b612ba4b2d79c2ffddd5660c00bf0a4302871734c +size 8503 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_1_en.png new file mode 100644 index 0000000000..e325515ffb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2606e39c21d12059e70c1bf95a80828c415ad1dc5fe27d2533dad58cec6e948 +size 8445 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_2_en.png new file mode 100644 index 0000000000..ce0291b9ac --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe8810842b717b145cfd850221a0b24608b7693d60735c15d2a2ea349449f781 +size 8015 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_3_en.png new file mode 100644 index 0000000000..adcd9b3f95 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dff87afdeca9e6de4a5053947270d93a61a23faf4a605e52b6b01e814b5d5e3e +size 6927 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_4_en.png new file mode 100644 index 0000000000..5883980ec4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea4febe911c963e720535c576a65842d2616ffb61103153a8a8738886c26eca7 +size 5251 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_0_en.png new file mode 100644 index 0000000000..2781abab27 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4cfea11d355bbe92346b49438ef59e4f7fe694039cdec8fdfc6ca097edbcdcea +size 8837 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_1_en.png new file mode 100644 index 0000000000..72b9d8a699 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4de82de886361037273b9a06fd29bff1b24a20a329d8590d97d03515ca21b43 +size 8733 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_2_en.png new file mode 100644 index 0000000000..674ba71a63 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cecf626b261bfbe72dfd9692a467d7205ee773db2afcb7adeabecfd9e390daf0 +size 8331 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_3_en.png new file mode 100644 index 0000000000..a765c7ff2d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e05d4bb9772b9535617ae9da1f461ed399fc9665dc2adf55a9d647ff640319b +size 7331 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_4_en.png new file mode 100644 index 0000000000..ea48f4c556 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_AvatarRow_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:690a1313ac8dd2cb84d7d560a16bdcd3eb0b65ad16d502da37efccecc4198b27 +size 5467 From c048cd4ec57bbce9d2d934b654c0409e9d8e2132 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 2 Jan 2025 08:13:05 +0100 Subject: [PATCH 9/9] Fix quality checks --- .../knockrequests/impl/banner/AvatarRow.kt | 64 +++++++++---------- .../impl/banner/KnockRequestsBannerView.kt | 2 + .../tests/konsist/KonsistClassNameTest.kt | 1 + 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt index ee8d8000e2..4f4df5c876 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/AvatarRow.kt @@ -29,9 +29,11 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize 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 kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList /** - * Draw a row of avatars (they must all have the same size), from start to end + * Draw a row of avatars (they must all have the same size), from start to end. * @param avatarDataList the avatars to render. Note: they will all be rendered, the caller may * want to limit the list size * @param modifier Jetpack Compose modifier @@ -40,7 +42,7 @@ import io.element.android.libraries.designsystem.text.toPx */ @Composable fun AvatarRow( - avatarDataList: List, + avatarDataList: ImmutableList, modifier: Modifier = Modifier, overlapRatio: Float = 0.5f, ) { @@ -56,34 +58,34 @@ fun AvatarRow( .forEachIndexed { index, avatarData -> Avatar( modifier = Modifier - .padding(start = avatarSize * (1 - overlapRatio) * (lastItemIndex - index)) - .graphicsLayer { - compositingStrategy = CompositingStrategy.Offscreen - } - .drawWithContent { - // Draw content and clear the pixels for the avatar on the left (right in RTL). - drawContent() - val xOffset = if (isRtl) { - size.width - avatarSizePx * (overlapRatio - 0.5f) - } else { - 0f + avatarSizePx * (overlapRatio - 0.5f) + .padding(start = avatarSize * (1 - overlapRatio) * (lastItemIndex - index)) + .graphicsLayer { + compositingStrategy = CompositingStrategy.Offscreen } - if (index < lastItemIndex) { - drawCircle( - color = Color.Black, - center = Offset( - x = xOffset, - y = size.height / 2, - ), - radius = avatarSizePx / 2, - blendMode = BlendMode.Clear, - ) + .drawWithContent { + // Draw content and clear the pixels for the avatar on the left (right in RTL). + drawContent() + val xOffset = if (isRtl) { + size.width - avatarSizePx * (overlapRatio - 0.5f) + } else { + 0f + avatarSizePx * (overlapRatio - 0.5f) + } + if (index < lastItemIndex) { + drawCircle( + color = Color.Black, + center = Offset( + x = xOffset, + y = size.height / 2, + ), + radius = avatarSizePx / 2, + blendMode = BlendMode.Clear, + ) + } } - } - .size(size = avatarSize) - // Keep internal padding, it has the advantage to not reduce the size of the Avatar image, - // which is already small in our use case. - .padding(2.dp), + .size(size = avatarSize) + // Keep internal padding, it has the advantage to not reduce the size of the Avatar image, + // which is already small in our use case. + .padding(2.dp), avatarData = avatarData, ) } @@ -113,15 +115,13 @@ internal fun AvatarRowRtlPreview(@PreviewParameter(OverlapRatioProvider::class) @Composable private fun ContentToPreview(overlapRatio: Float) { AvatarRow( - avatarDataList = listOf( - "A", "B", "C" - ).map { + avatarDataList = listOf("A", "B", "C").map { AvatarData( id = it, name = it, size = AvatarSize.RoomListItem, ) - }, + }.toImmutableList(), overlapRatio = overlapRatio, ) } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index a7307d605b..9dc4e63195 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -49,6 +49,7 @@ import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList private const val MAX_AVATAR_COUNT = 3 @@ -210,6 +211,7 @@ private fun KnockRequestAvatarListView( .map { knockRequest -> knockRequest.getAvatarData(AvatarSize.KnockRequestBanner) } + .toImmutableList() AvatarRow( avatarDataList = avatars, modifier = modifier, diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt index 9d8e23e7dc..7a75ff9f1a 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt @@ -50,6 +50,7 @@ class KonsistClassNameTest { .withAllParentsOf(PreviewParameterProvider::class) .withoutName( "AspectRatioProvider", + "OverlapRatioProvider", ) .also { // Check that classes are actually found