mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Display the user status emoji beside the avatar
This commit is contained in:
+61
-89
@@ -12,6 +12,7 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
@@ -43,18 +44,10 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.Color
|
||||||
import androidx.compose.ui.graphics.CompositingStrategy
|
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.Density
|
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 androidx.compose.ui.unit.dp
|
||||||
import io.element.android.appconfig.RoomListConfig
|
import io.element.android.appconfig.RoomListConfig
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
@@ -296,11 +289,15 @@ private fun NavigationIcon(
|
|||||||
onAccountSwitch: (SessionId) -> Unit,
|
onAccountSwitch: (SessionId) -> Unit,
|
||||||
onClick: () -> 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) {
|
if (currentUserAndNeighbors.size == 1) {
|
||||||
AccountIcon(
|
AccountIcon(
|
||||||
matrixUser = currentUserAndNeighbors.single(),
|
matrixUser = currentUserAndNeighbors.single(),
|
||||||
isCurrentAccount = true,
|
isCurrentAccount = true,
|
||||||
showAvatarIndicator = showAvatarIndicator,
|
showAvatarIndicator = showAvatarIndicator,
|
||||||
|
reserveStatusEmojiSlot = reserveStatusEmojiSlot,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -321,6 +318,7 @@ private fun NavigationIcon(
|
|||||||
matrixUser = currentUserAndNeighbors[page],
|
matrixUser = currentUserAndNeighbors[page],
|
||||||
isCurrentAccount = page == 1,
|
isCurrentAccount = page == 1,
|
||||||
showAvatarIndicator = page == 1 && showAvatarIndicator,
|
showAvatarIndicator = page == 1 && showAvatarIndicator,
|
||||||
|
reserveStatusEmojiSlot = reserveStatusEmojiSlot,
|
||||||
onClick = if (page == 1) {
|
onClick = if (page == 1) {
|
||||||
onClick
|
onClick
|
||||||
} else {
|
} else {
|
||||||
@@ -336,95 +334,69 @@ private fun AccountIcon(
|
|||||||
matrixUser: MatrixUser,
|
matrixUser: MatrixUser,
|
||||||
isCurrentAccount: Boolean,
|
isCurrentAccount: Boolean,
|
||||||
showAvatarIndicator: Boolean,
|
showAvatarIndicator: Boolean,
|
||||||
|
reserveStatusEmojiSlot: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val testTag = if (isCurrentAccount) Modifier.testTag(TestTags.homeScreenSettings) else Modifier
|
val testTag = if (isCurrentAccount) Modifier.testTag(TestTags.homeScreenSettings) else Modifier
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
Box(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier,
|
||||||
.then(testTag)
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
.minimumInteractiveComponentSize()
|
|
||||||
.clickable(
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
onClick = onClick,
|
|
||||||
indication = ripple(bounded = false),
|
|
||||||
),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
) {
|
||||||
val avatarData by remember(matrixUser) {
|
Box(
|
||||||
derivedStateOf {
|
modifier = Modifier
|
||||||
matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar)
|
.then(testTag)
|
||||||
|
.minimumInteractiveComponentSize()
|
||||||
|
.clickable(
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
onClick = onClick,
|
||||||
|
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()
|
if (reserveStatusEmojiSlot) {
|
||||||
val avatarModifier = if (statusEmoji != null) {
|
StatusEmoji(
|
||||||
Modifier.eraseStatusEmojiBackground(
|
emoji = matrixUser.displayedStatus?.toEmojiText(),
|
||||||
parentSize = AvatarSize.CurrentUserTopBar.dp,
|
modifier = Modifier.offset(x = statusEmojiStartOffset),
|
||||||
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)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val statusEmojiBadgeSize = 20.dp
|
private val statusEmojiSize = 20.dp
|
||||||
private val statusEmojiBadgeRadius = statusEmojiBadgeSize / 2
|
|
||||||
private val statusEmojiBadgeOffset = 8.dp
|
|
||||||
|
|
||||||
private fun Modifier.eraseStatusEmojiBackground(
|
// The avatar is centered inside a 48dp touch target, so there is already 8dp of free space
|
||||||
parentSize: Dp,
|
// on its trailing side. Compensate it to end up with a 4dp gap after the avatar itself.
|
||||||
layoutDirection: LayoutDirection,
|
private val statusEmojiStartOffset = 4.dp - (48.dp - AvatarSize.CurrentUserTopBar.dp) / 2
|
||||||
): 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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun StatusEmojiBadge(
|
private fun StatusEmoji(
|
||||||
emoji: String,
|
emoji: String?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
@@ -433,16 +405,16 @@ private fun StatusEmojiBadge(
|
|||||||
Density(density = density.density, fontScale = 1f)
|
Density(density = density.density, fontScale = 1f)
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier.size(statusEmojiSize),
|
||||||
.offset(x = statusEmojiBadgeOffset)
|
|
||||||
.size(statusEmojiBadgeSize),
|
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(LocalDensity provides fixedFontScaleDensity) {
|
if (emoji != null) {
|
||||||
Text(
|
CompositionLocalProvider(LocalDensity provides fixedFontScaleDensity) {
|
||||||
text = emoji,
|
Text(
|
||||||
style = ElementTheme.typography.fontBodyMdRegular,
|
text = emoji,
|
||||||
)
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user