mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Display user status next to display names across rooms and profiles
This commit is contained in:
+2
-1
@@ -19,6 +19,7 @@ import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
@@ -50,7 +51,7 @@ fun CheckableUserRow(
|
||||
UserRow(
|
||||
modifier = rowModifier,
|
||||
avatarData = data.avatarData,
|
||||
name = data.name,
|
||||
name = AnnotatedString(data.name),
|
||||
subtext = data.subtext,
|
||||
enabled = enabled,
|
||||
)
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
||||
/**
|
||||
* Component to display side by side a display name and an optional status.
|
||||
*/
|
||||
@Composable
|
||||
fun DisplayNameWithStatus(
|
||||
name: String,
|
||||
status: String?,
|
||||
style: TextStyle,
|
||||
modifier: Modifier = Modifier,
|
||||
nameColor: Color = Color.Unspecified,
|
||||
nameFontStyle: FontStyle? = null,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = name,
|
||||
modifier = Modifier
|
||||
.weight(1f, fill = false)
|
||||
.clipToBounds(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = style,
|
||||
color = nameColor,
|
||||
fontStyle = nameFontStyle,
|
||||
)
|
||||
if (status != null) {
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(
|
||||
text = status,
|
||||
style = style,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-7
@@ -32,6 +32,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
import io.element.android.libraries.matrix.ui.model.getBestName
|
||||
import io.element.android.libraries.matrix.ui.model.toEmojiText
|
||||
|
||||
@Composable
|
||||
fun MatrixUserHeader(
|
||||
@@ -58,13 +59,11 @@ fun MatrixUserHeader(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
// Name
|
||||
Text(
|
||||
modifier = Modifier.clipToBounds(),
|
||||
text = matrixUser.getBestName(),
|
||||
maxLines = 1,
|
||||
style = ElementTheme.typography.fontHeadingMdRegular,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
DisplayNameWithStatus(
|
||||
name = matrixUser.getBestName(),
|
||||
status = matrixUser.displayedStatus?.toEmojiText(),
|
||||
nameColor = ElementTheme.colors.textPrimary,
|
||||
style = ElementTheme.typography.fontHeadingMdRegular
|
||||
)
|
||||
// Id
|
||||
if (matrixUser.displayName.isNullOrEmpty().not()) {
|
||||
|
||||
+4
@@ -21,6 +21,7 @@ import io.element.android.libraries.designsystem.preview.USER_NAME_SUSIE
|
||||
import io.element.android.libraries.designsystem.preview.USER_NAME_VICTOR
|
||||
import io.element.android.libraries.designsystem.preview.USER_NAME_WALTER
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
||||
open class MatrixUserProvider : PreviewParameterProvider<MatrixUser> {
|
||||
@@ -28,6 +29,7 @@ open class MatrixUserProvider : PreviewParameterProvider<MatrixUser> {
|
||||
get() = sequenceOf(
|
||||
aMatrixUser(),
|
||||
aMatrixUser(displayName = null),
|
||||
aMatrixUser(displayedStatus = DisplayedStatus.InCall(0L) ),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -43,10 +45,12 @@ fun aMatrixUser(
|
||||
id: String? = null,
|
||||
displayName: String? = USER_NAME_ALICE,
|
||||
avatarUrl: String? = null,
|
||||
displayedStatus: DisplayedStatus? = null,
|
||||
) = MatrixUser(
|
||||
userId = UserId(id ?: "@${displayName?.lowercase()?.replace(" ", "_") ?: "id"}:server.org"),
|
||||
displayName = displayName,
|
||||
avatarUrl = avatarUrl,
|
||||
displayedStatus = displayedStatus,
|
||||
)
|
||||
|
||||
fun aMatrixUserList() = listOf(
|
||||
|
||||
+10
-1
@@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.ui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -19,6 +20,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
import io.element.android.libraries.matrix.ui.model.getBestName
|
||||
import io.element.android.libraries.matrix.ui.model.toEmojiText
|
||||
|
||||
@Composable
|
||||
fun MatrixUserRow(
|
||||
@@ -29,7 +31,14 @@ fun MatrixUserRow(
|
||||
trailingContent: @Composable (() -> Unit)? = null,
|
||||
) = UserRow(
|
||||
avatarData = matrixUser.getAvatarData(avatarSize),
|
||||
name = matrixUser.getBestName(),
|
||||
nameContent = {
|
||||
DisplayNameWithStatus(
|
||||
name = matrixUser.getBestName(),
|
||||
status = matrixUser.displayedStatus?.toEmojiText(),
|
||||
nameColor = ElementTheme.colors.textPrimary,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
)
|
||||
},
|
||||
subtext = if (matrixUser.displayName.isNullOrEmpty()) null else matrixUser.userId.value,
|
||||
modifier = modifier,
|
||||
verticalSpaceWidth = verticalSpaceWidth,
|
||||
|
||||
+32
-9
@@ -18,6 +18,7 @@ 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.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -30,7 +31,36 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||
@Composable
|
||||
internal fun UserRow(
|
||||
avatarData: AvatarData,
|
||||
name: String,
|
||||
name: AnnotatedString,
|
||||
subtext: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
verticalSpaceWidth: Dp = 12.dp,
|
||||
trailingContent: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
UserRow(
|
||||
avatarData = avatarData,
|
||||
nameContent = {
|
||||
Text(
|
||||
modifier = Modifier.clipToBounds(),
|
||||
text = name,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
)
|
||||
},
|
||||
subtext = subtext,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
verticalSpaceWidth = verticalSpaceWidth,
|
||||
trailingContent = trailingContent,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun UserRow(
|
||||
avatarData: AvatarData,
|
||||
nameContent: @Composable () -> Unit,
|
||||
subtext: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
@@ -52,14 +82,7 @@ internal fun UserRow(
|
||||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
// Name
|
||||
Text(
|
||||
modifier = Modifier.clipToBounds(),
|
||||
text = name,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = if (enabled) ElementTheme.colors.textPrimary else ElementTheme.colors.textDisabled,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
)
|
||||
nameContent()
|
||||
// Id
|
||||
subtext?.let {
|
||||
Text(
|
||||
|
||||
+6
@@ -13,6 +13,7 @@ import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.drop
|
||||
@@ -31,4 +32,9 @@ class RoomMemberProfilesCache {
|
||||
fun getDisplayName(userId: UserId): String? {
|
||||
return cache.value[userId]?.disambiguatedDisplayName
|
||||
}
|
||||
|
||||
fun getDisplayedStatus(userId: UserId): DisplayedStatus? {
|
||||
return cache.value[userId]?.displayedStatus
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
@@ -167,4 +167,5 @@ fun aProfileDetailsReady(
|
||||
displayName = displayName,
|
||||
displayNameAmbiguous = displayNameAmbiguous,
|
||||
avatarUrl = avatarUrl,
|
||||
displayedStatus = null,
|
||||
)
|
||||
|
||||
+18
-1
@@ -24,6 +24,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
|
||||
import io.element.android.libraries.matrix.ui.model.toText
|
||||
|
||||
// https://www.figma.com/file/Ni6Ii8YKtmXCKYNE90cC67/Timeline-(new)?type=design&node-id=917-80169&mode=design&t=A0CJCBbMqR8NOwUQ-0
|
||||
@Composable
|
||||
@@ -54,6 +55,19 @@ fun SenderName(
|
||||
SecondaryText(text = senderId.value, mode = senderNameMode)
|
||||
}
|
||||
}
|
||||
val userStatus = senderProfile.displayedStatus
|
||||
if (senderNameMode is SenderNameMode.Timeline && userStatus != null) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.clipToBounds()
|
||||
.alignByBaseline(),
|
||||
text = userStatus.toText(),
|
||||
style = ElementTheme.typography.fontBodyMdMedium,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,13 +88,16 @@ private fun RowScope.MainText(
|
||||
SenderNameMode.ActionList,
|
||||
SenderNameMode.Reply -> Modifier
|
||||
}
|
||||
|
||||
val color = when (mode) {
|
||||
is SenderNameMode.Timeline -> mode.mainColor
|
||||
SenderNameMode.ActionList,
|
||||
SenderNameMode.Reply -> ElementTheme.colors.textPrimary
|
||||
}
|
||||
Text(
|
||||
modifier = modifier.clipToBounds(),
|
||||
modifier = modifier
|
||||
.clipToBounds()
|
||||
.weight(1f, fill = false),
|
||||
text = text,
|
||||
style = style,
|
||||
color = color,
|
||||
|
||||
+4
-1
@@ -12,6 +12,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
|
||||
data class SenderNameData(
|
||||
val userId: UserId,
|
||||
@@ -47,12 +48,14 @@ open class SenderNameDataProvider : PreviewParameterProvider<SenderNameData> {
|
||||
private fun aSenderNameData(
|
||||
senderNameMode: SenderNameMode,
|
||||
displayNameAmbiguous: Boolean = false,
|
||||
displayedStatus: DisplayedStatus? = null,
|
||||
) = SenderNameData(
|
||||
userId = UserId("@alice:${senderNameMode.javaClass.simpleName.lowercase()}"),
|
||||
profileDetails = ProfileDetails.Ready(
|
||||
displayName = "Alice ${senderNameMode.javaClass.simpleName}",
|
||||
displayNameAmbiguous = displayNameAmbiguous,
|
||||
avatarUrl = null
|
||||
avatarUrl = null,
|
||||
displayedStatus = displayedStatus,
|
||||
),
|
||||
senderNameMode = senderNameMode,
|
||||
)
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.ui.model
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
import io.element.android.libraries.matrix.ui.R
|
||||
|
||||
fun DisplayedStatus.toEmojiText(): String = when (this) {
|
||||
is DisplayedStatus.UserSet -> status.emoji
|
||||
is DisplayedStatus.InCall -> "🎧"
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisplayedStatus.toText(): String = when (this) {
|
||||
is DisplayedStatus.UserSet -> "${status.emoji} ${status.text}"
|
||||
is DisplayedStatus.InCall -> "🎧 ${stringResource(R.string.common_user_status_on_a_call)}"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2026 Element Creations Ltd.
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<resources>
|
||||
<string name="common_user_status_on_a_call">On a call</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user