diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt index 7af1ccbda1..e799f44e25 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt @@ -9,9 +9,11 @@ package io.element.android.features.home.impl.components import androidx.compose.foundation.clickable +import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides @@ -43,18 +45,10 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment 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.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import io.element.android.appconfig.RoomListConfig import io.element.android.compound.theme.ElementTheme @@ -296,11 +290,15 @@ private fun NavigationIcon( onAccountSwitch: (SessionId) -> Unit, onClick: () -> Unit, ) { + // Reserve the status emoji slot for all the accounts, else the width of the navigation icon, + // and so the position of the title, would change while scrolling the pager. + val reserveStatusEmojiSlot = currentUserAndNeighbors.any { it.displayedStatus != null } if (currentUserAndNeighbors.size == 1) { AccountIcon( matrixUser = currentUserAndNeighbors.single(), isCurrentAccount = true, showAvatarIndicator = showAvatarIndicator, + reserveStatusEmojiSlot = reserveStatusEmojiSlot, onClick = onClick, ) } else { @@ -321,6 +319,7 @@ private fun NavigationIcon( matrixUser = currentUserAndNeighbors[page], isCurrentAccount = page == 1, showAvatarIndicator = page == 1 && showAvatarIndicator, + reserveStatusEmojiSlot = reserveStatusEmojiSlot, onClick = if (page == 1) { onClick } else { @@ -336,95 +335,74 @@ private fun AccountIcon( matrixUser: MatrixUser, isCurrentAccount: Boolean, showAvatarIndicator: Boolean, + reserveStatusEmojiSlot: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, ) { val testTag = if (isCurrentAccount) Modifier.testTag(TestTags.homeScreenSettings) else Modifier val interactionSource = remember { MutableInteractionSource() } - Box( + Row( modifier = modifier .then(testTag) - .minimumInteractiveComponentSize() .clickable( interactionSource = interactionSource, onClick = onClick, - indication = ripple(bounded = false), + // The ripple is rendered on the avatar only, see below. + indication = null, ), - contentAlignment = Alignment.Center, + verticalAlignment = Alignment.CenterVertically, ) { - val avatarData by remember(matrixUser) { - derivedStateOf { - matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar) + Box( + modifier = Modifier + .minimumInteractiveComponentSize() + .indication( + interactionSource = interactionSource, + indication = ripple(bounded = false), + ), + contentAlignment = Alignment.Center, + ) { + val avatarData by remember(matrixUser) { + derivedStateOf { + matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar) + } + } + Avatar( + avatarData = avatarData, + avatarType = AvatarType.User, + contentDescription = if (isCurrentAccount) { + if (showAvatarIndicator) { + stringResource(CommonStrings.a11y_settings_with_required_action) + } else { + stringResource(CommonStrings.common_settings) + } + } else { + null + }, + ) + if (showAvatarIndicator) { + RedIndicatorAtom( + modifier = Modifier.align(Alignment.TopEnd) + ) } } - val statusEmoji = matrixUser.displayedStatus?.toEmojiText() - val avatarModifier = if (statusEmoji != null) { - Modifier.eraseStatusEmojiBackground( - parentSize = AvatarSize.CurrentUserTopBar.dp, - layoutDirection = LocalLayoutDirection.current, - ) - } else { - Modifier - } - Avatar( - avatarData = avatarData, - avatarType = AvatarType.User, - modifier = avatarModifier, - contentDescription = if (isCurrentAccount) { - if (showAvatarIndicator) { - stringResource(CommonStrings.a11y_settings_with_required_action) - } else { - stringResource(CommonStrings.common_settings) - } - } else { - null - }, - ) - if (statusEmoji != null) { - StatusEmojiBadge( - emoji = statusEmoji, - modifier = Modifier.align(Alignment.BottomEnd), - ) - } - if (showAvatarIndicator) { - RedIndicatorAtom( - modifier = Modifier.align(Alignment.TopEnd) + if (reserveStatusEmojiSlot) { + StatusEmoji( + emoji = matrixUser.displayedStatus?.toEmojiText(), + modifier = Modifier.offset(x = statusEmojiStartOffset), ) } } } -private val statusEmojiBadgeSize = 20.dp -private val statusEmojiBadgeRadius = statusEmojiBadgeSize / 2 -private val statusEmojiBadgeOffset = 8.dp +private val statusEmojiSize = 20.dp -private fun Modifier.eraseStatusEmojiBackground( - parentSize: Dp, - layoutDirection: LayoutDirection, -): Modifier = this - .graphicsLayer { - compositingStrategy = CompositingStrategy.Offscreen - } - .drawWithContent { - drawContent() - drawCircle( - color = Color.Black, - center = Offset( - x = if (layoutDirection == LayoutDirection.Ltr) { - (parentSize - statusEmojiBadgeRadius + statusEmojiBadgeOffset).toPx() - } else { - (statusEmojiBadgeRadius - statusEmojiBadgeOffset).toPx() - }, - y = size.height - statusEmojiBadgeRadius.toPx(), - ), - radius = statusEmojiBadgeRadius.toPx(), - blendMode = BlendMode.Clear, - ) - } +// The avatar is centered inside a 48dp touch target, so there is already 8dp of free space +// on its trailing side. Compensate it to end up with a 4dp gap after the avatar itself. +private val statusEmojiStartOffset = 4.dp - (48.dp - AvatarSize.CurrentUserTopBar.dp) / 2 @Composable -private fun StatusEmojiBadge( - emoji: String, +private fun StatusEmoji( + emoji: String?, modifier: Modifier = Modifier, ) { val density = LocalDensity.current @@ -433,16 +411,16 @@ private fun StatusEmojiBadge( Density(density = density.density, fontScale = 1f) } Box( - modifier = modifier - .offset(x = statusEmojiBadgeOffset) - .size(statusEmojiBadgeSize), + modifier = modifier.size(statusEmojiSize), contentAlignment = Alignment.Center, ) { - CompositionLocalProvider(LocalDensity provides fixedFontScaleDensity) { - Text( - text = emoji, - style = ElementTheme.typography.fontBodyMdRegular, - ) + if (emoji != null) { + CompositionLocalProvider(LocalDensity provides fixedFontScaleDensity) { + Text( + text = emoji, + style = ElementTheme.typography.fontBodyMdRegular, + ) + } } } } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeader.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeader.kt index 188a8bc1a4..875925cda9 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeader.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeader.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp @@ -57,11 +58,13 @@ fun MatrixUserHeader( verticalArrangement = Arrangement.spacedBy(2.dp) ) { // Name - DisplayNameWithStatus( - name = matrixUser.getBestName(), - status = matrixUser.displayedStatus, - nameColor = ElementTheme.colors.textPrimary, + Text( + modifier = Modifier.clipToBounds(), + text = matrixUser.getBestName(), + maxLines = 1, style = ElementTheme.typography.fontHeadingMdRegular, + overflow = TextOverflow.Ellipsis, + color = ElementTheme.colors.textPrimary, ) // Id if (matrixUser.displayName.isNullOrEmpty().not()) { diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Day_0_en.png index dcc43b624c..928253c325 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9007ccadff05518151748860ab9b34ba35f10d459a4301a36be424330630b45 -size 23721 +oid sha256:12c940fe32952fd545a24e365ca046c3dc7da55925253d9e4f8faf00e8d3b4af +size 23864 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Night_0_en.png index 269cb08cf9..7c3661f7fb 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21e43a869049ba9361303794ba3aabb7d7f4c4b9f1b9c609abd6fd40bd2f562a -size 21697 +oid sha256:282b13862c773c4d220b76aa7dc11f3387863ee6bbf7af9ffa79b7df20b6bfaa +size 21859 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeViewA11y_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeViewA11y_en.png index a1e2d12cd9..3c453772af 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeViewA11y_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeViewA11y_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ca00f66f5c785e8b453c248f70b37c97e84d7524ff35f7c5a1f88c4091c10a4 -size 143901 +oid sha256:2c14c1e0d871dd12f482c80177955d3f99a9408287a12d070d649837e6785962 +size 144572 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.user_UserPreferences_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.user_UserPreferences_Day_2_en.png index 0a34d0b390..03d5ad5880 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.user_UserPreferences_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.user_UserPreferences_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3ecf110c258f4ec5fd5d2a4126395e248d73d894afd54a0135dc710f680c0ee -size 12395 +oid sha256:0dfea70e781debf7a293aee2967c3da314101020698fee97d3551e25f5f378d0 +size 10655 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.user_UserPreferences_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.user_UserPreferences_Night_2_en.png index 58a30a7304..27f51b2778 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.user_UserPreferences_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.user_UserPreferences_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6bc66cd4269ee462103e77bfa24723a740d8a91dc7e06ca808b8109e74d1784f -size 12127 +oid sha256:62e127900d00756ecc806eb9d8efb6a11cd9cdcecae6b4bab643b7306a06be2b +size 10435 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_MatrixUserHeader_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_MatrixUserHeader_Day_2_en.png index 0a34d0b390..03d5ad5880 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_MatrixUserHeader_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_MatrixUserHeader_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3ecf110c258f4ec5fd5d2a4126395e248d73d894afd54a0135dc710f680c0ee -size 12395 +oid sha256:0dfea70e781debf7a293aee2967c3da314101020698fee97d3551e25f5f378d0 +size 10655 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_MatrixUserHeader_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_MatrixUserHeader_Night_2_en.png index 58a30a7304..27f51b2778 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_MatrixUserHeader_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_MatrixUserHeader_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6bc66cd4269ee462103e77bfa24723a740d8a91dc7e06ca808b8109e74d1784f -size 12127 +oid sha256:62e127900d00756ecc806eb9d8efb6a11cd9cdcecae6b4bab643b7306a06be2b +size 10435