Merge pull request #4103 from element-hq/feature/bma/fixDmAvatarRtl
Fix dm avatar rtl
This commit is contained in:
commit
a411ac87ae
26 changed files with 245 additions and 49 deletions
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* 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.runtime.CompositionLocalProvider
|
||||
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.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
|
||||
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
|
||||
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.
|
||||
* @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: ImmutableList<AvatarData>,
|
||||
modifier: Modifier = Modifier,
|
||||
overlapRatio: Float = 0.5f,
|
||||
) {
|
||||
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
|
||||
Box(
|
||||
modifier = modifier,
|
||||
) {
|
||||
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 * (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 = 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),
|
||||
avatarData = avatarData,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@PreviewsDayNight
|
||||
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").map {
|
||||
AvatarData(
|
||||
id = it,
|
||||
name = it,
|
||||
size = AvatarSize.RoomListItem,
|
||||
)
|
||||
}.toImmutableList(),
|
||||
overlapRatio = overlapRatio,
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -56,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
|
||||
|
||||
|
|
@ -120,9 +114,9 @@ private fun KnockRequestsBannerContent(
|
|||
}
|
||||
|
||||
Column(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = 16.dp)
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = 16.dp)
|
||||
) {
|
||||
Row {
|
||||
KnockRequestAvatarView(
|
||||
|
|
@ -212,44 +206,16 @@ private fun KnockRequestAvatarListView(
|
|||
knockRequests: ImmutableList<KnockRequestPresentable>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val avatarSize = AvatarSize.KnockRequestBanner.dp
|
||||
Box(
|
||||
val avatars = knockRequests
|
||||
.take(MAX_AVATAR_COUNT)
|
||||
.map { knockRequest ->
|
||||
knockRequest.getAvatarData(AvatarSize.KnockRequestBanner)
|
||||
}
|
||||
.toImmutableList()
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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<Float> {
|
||||
override val values: Sequence<Float> = sequenceOf(
|
||||
0f,
|
||||
0.25f,
|
||||
0.5f,
|
||||
0.75f,
|
||||
1f
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -45,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
|
||||
|
|
@ -58,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,
|
||||
|
|
@ -106,3 +115,13 @@ internal fun DmAvatarsPreview() = ElementThemedPreview {
|
|||
openOtherAvatarPreview = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.Avatars)
|
||||
@Composable
|
||||
internal fun DmAvatarsRtlPreview() {
|
||||
CompositionLocalProvider(
|
||||
LocalLayoutDirection provides LayoutDirection.Rtl,
|
||||
) {
|
||||
DmAvatarsPreview()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ class KonsistClassNameTest {
|
|||
.withAllParentsOf(PreviewParameterProvider::class)
|
||||
.withoutName(
|
||||
"AspectRatioProvider",
|
||||
"OverlapRatioProvider",
|
||||
)
|
||||
.also {
|
||||
// Check that classes are actually found
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d318e0e4fa1bd8a123057dca6efc9c92c5dc96614dad3514813559274288e115
|
||||
size 8520
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b0bee0c2e2c712621bbc92c430807c758429ef1179394b107c393f9236295bbb
|
||||
size 8447
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4a92d34b2ac73b7d3341f39f46bbf0a4dcf9862e9cccdfc0111b64c562930f03
|
||||
size 7720
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:79a021e58c28d0e0cbb50f99304dae5c8c2ec95ca99a02d7876a167adc911e7f
|
||||
size 6948
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ea4febe911c963e720535c576a65842d2616ffb61103153a8a8738886c26eca7
|
||||
size 5251
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:33ad034f7be86da7a2399d11c0991b5d6997ddaffa9f26b20a456f1b22666b70
|
||||
size 8825
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5784d917b0176cfa6b584804b28d8592ed97fe6e1f60a805619cce3170acb802
|
||||
size 8722
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:06a0cef40417aa586e85b5896e763279e2c3df6121ffd65b9b85129e2a5ea4f4
|
||||
size 8023
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ac5300b143c12ebf39fb6b0074677b5f16760217cca3823229304838e322e424
|
||||
size 7375
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:690a1313ac8dd2cb84d7d560a16bdcd3eb0b65ad16d502da37efccecc4198b27
|
||||
size 5467
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:836e6d27c46a85d5a8d6fd8b612ba4b2d79c2ffddd5660c00bf0a4302871734c
|
||||
size 8503
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c2606e39c21d12059e70c1bf95a80828c415ad1dc5fe27d2533dad58cec6e948
|
||||
size 8445
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fe8810842b717b145cfd850221a0b24608b7693d60735c15d2a2ea349449f781
|
||||
size 8015
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dff87afdeca9e6de4a5053947270d93a61a23faf4a605e52b6b01e814b5d5e3e
|
||||
size 6927
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ea4febe911c963e720535c576a65842d2616ffb61103153a8a8738886c26eca7
|
||||
size 5251
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4cfea11d355bbe92346b49438ef59e4f7fe694039cdec8fdfc6ca097edbcdcea
|
||||
size 8837
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d4de82de886361037273b9a06fd29bff1b24a20a329d8590d97d03515ca21b43
|
||||
size 8733
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cecf626b261bfbe72dfd9692a467d7205ee773db2afcb7adeabecfd9e390daf0
|
||||
size 8331
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6e05d4bb9772b9535617ae9da1f461ed399fc9665dc2adf55a9d647ff640319b
|
||||
size 7331
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:690a1313ac8dd2cb84d7d560a16bdcd3eb0b65ad16d502da37efccecc4198b27
|
||||
size 5467
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b0007ee6df39ec6b8f180cab21a99f97adef39deb1055ba8d179e5fee5ca1203
|
||||
size 13856
|
||||
Loading…
Add table
Add a link
Reference in a new issue