Display user status next to display names across rooms and profiles

This commit is contained in:
ganfra
2026-06-24 17:59:08 +02:00
parent 18034e02a2
commit 06a903718b
46 changed files with 386 additions and 89 deletions
@@ -319,6 +319,7 @@ class MessagesPresenter(
appName = buildMeta.applicationName,
pinnedMessagesBannerState = pinnedMessagesBannerState,
dmUserVerificationState = dmUserVerificationState,
dmUserStatus = dmRoomMember?.displayedStatus,
roomMemberModerationState = roomMemberModerationState,
topBarSharedHistoryIcon = topBarSharedHistoryIcon,
successorRoom = roomInfo.successorRoom,
@@ -27,6 +27,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.user.DisplayedStatus
import kotlinx.collections.immutable.ImmutableList
data class MessagesState(
@@ -53,6 +54,7 @@ data class MessagesState(
val appName: String,
val pinnedMessagesBannerState: PinnedMessagesBannerState,
val dmUserVerificationState: IdentityState?,
val dmUserStatus: DisplayedStatus?,
val roomMemberModerationState: RoomMemberModerationState,
/** Type of "shared history" icon to show in the top bar. */
val topBarSharedHistoryIcon: SharedHistoryIcon,
@@ -50,6 +50,7 @@ import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.user.DisplayedStatus
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
@@ -129,6 +130,7 @@ fun aMessagesState(
hasUnreadThreads = false,
),
isCurrentlySharingLiveLocationInRoom: Boolean = false,
dmUserStatus: DisplayedStatus? = null,
eventSink: (MessagesEvent) -> Unit = {},
) = MessagesState(
roomId = RoomId("!id:domain"),
@@ -159,6 +161,7 @@ fun aMessagesState(
successorRoom = successorRoom,
threads = threads,
showLiveLocationShareBanner = isCurrentlySharingLiveLocationInRoom,
dmUserStatus = dmUserStatus,
eventSink = eventSink,
)
@@ -244,6 +244,7 @@ fun MessagesView(
heroes = state.heroes,
dmUserIdentityState = state.dmUserVerificationState,
sharedHistoryIcon = state.topBarSharedHistoryIcon,
dmUserStatus = state.dmUserStatus,
onBackClick = { hidingKeyboard { onBackClick() } },
onRoomDetailsClick = { hidingKeyboard { onRoomDetailsClick() } },
menuActions = {
@@ -41,7 +41,9 @@ import io.element.android.libraries.matrix.api.core.RoomId
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.room.RoomMembershipState
import io.element.android.libraries.matrix.ui.components.DisplayNameWithStatus
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.model.toEmojiText
import io.element.android.libraries.slashcommands.api.SlashCommandSuggestion
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
import kotlinx.collections.immutable.ImmutableList
@@ -112,12 +114,16 @@ private fun SuggestionItemView(
is ResolvedSuggestion.Member -> AvatarType.User
is ResolvedSuggestion.Command -> null
}
val title = when (suggestion) {
val title: String? = when (suggestion) {
is ResolvedSuggestion.AtRoom -> stringResource(R.string.screen_room_mentions_at_room_title)
is ResolvedSuggestion.Member -> suggestion.roomMember.displayName
is ResolvedSuggestion.Member -> suggestion.roomMember.displayName ?: suggestion.roomMember.userId.value
is ResolvedSuggestion.Alias -> suggestion.roomName
is ResolvedSuggestion.Command -> suggestion.command.command
}
val statusEmoji = when (suggestion) {
is ResolvedSuggestion.Member -> suggestion.roomMember.displayedStatus?.toEmojiText()
else -> null
}
val details = when (suggestion) {
is ResolvedSuggestion.AtRoom,
is ResolvedSuggestion.Member,
@@ -148,11 +154,11 @@ private fun SuggestionItemView(
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
title?.let {
Text(
text = it,
DisplayNameWithStatus(
name = title,
status = statusEmoji,
style = ElementTheme.typography.fontBodyLgRegular,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
nameColor = ElementTheme.colors.textPrimary,
)
}
details?.let {
@@ -191,6 +197,7 @@ internal fun SuggestionsPickerViewPreview() {
role = RoomMember.Role.User,
membershipChangeReason = null,
isServiceMember = false,
displayedStatus = null,
)
val anAlias = remember { RoomAlias("#room:domain.org") }
SuggestionsPickerView(
@@ -383,7 +383,12 @@ fun aThreadListItem(
fun aThreadListItemEvent(
threadId: ThreadId = ThreadId("\$a-thread-id"),
senderId: UserId = UserId("@a-user-id:server"),
senderProfile: ProfileDetails = ProfileDetails.Ready(displayName = USER_NAME_ALICE, displayNameAmbiguous = false, avatarUrl = null),
senderProfile: ProfileDetails = ProfileDetails.Ready(
displayName = USER_NAME_ALICE,
displayNameAmbiguous = false,
avatarUrl = null,
displayedStatus = null,
),
isOwn: Boolean = false,
content: EventContent = MessageContent(
body = "Hello world!",
@@ -166,6 +166,7 @@ internal fun MessageShieldViewPreview() {
displayName = USER_NAME_ALICE,
displayNameAmbiguous = false,
avatarUrl = null,
displayedStatus = null,
),
)
)
@@ -962,6 +962,7 @@ internal fun TimelineItemEventRowWithThreadSummaryPreview() = ElementPreview {
displayName = USER_NAME_ALICE,
avatarUrl = null,
displayNameAmbiguous = false,
displayedStatus = null,
),
timestamp = 0L,
)
@@ -1140,6 +1141,7 @@ internal fun ThreadSummaryViewPreview() {
displayName = USER_NAME_ALICE,
avatarUrl = null,
displayNameAmbiguous = true,
displayedStatus = null,
),
timestamp = 0L,
)
@@ -26,7 +26,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
@@ -46,11 +45,13 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.ROOM_NAME
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.api.user.DisplayedStatus
import io.element.android.libraries.matrix.ui.components.DisplayNameWithStatus
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.model.toEmojiText
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@@ -65,6 +66,7 @@ internal fun MessagesViewTopBar(
heroes: ImmutableList<AvatarData>,
dmUserIdentityState: IdentityState?,
sharedHistoryIcon: SharedHistoryIcon,
dmUserStatus: DisplayedStatus?,
onRoomDetailsClick: () -> Unit,
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
@@ -91,6 +93,7 @@ internal fun MessagesViewTopBar(
roomAvatar = roomAvatar,
isTombstoned = isTombstoned,
heroes = heroes,
dmUserStatus = dmUserStatus,
modifier = titleModifier
)
@@ -144,6 +147,7 @@ private fun RoomAvatarAndNameRow(
roomAvatar: AvatarData,
heroes: ImmutableList<AvatarData>,
isTombstoned: Boolean,
dmUserStatus: DisplayedStatus?,
modifier: Modifier = Modifier
) {
Row(
@@ -157,14 +161,13 @@ private fun RoomAvatarAndNameRow(
isTombstoned = isTombstoned,
),
)
Text(
modifier = Modifier
.padding(start = 8.dp),
text = roomName ?: stringResource(CommonStrings.common_no_room_name),
DisplayNameWithStatus(
name = roomName ?: stringResource(CommonStrings.common_no_room_name),
status = dmUserStatus?.toEmojiText(),
modifier = Modifier.padding(start = 8.dp),
style = ElementTheme.typography.fontBodyLgMedium,
fontStyle = FontStyle.Italic.takeIf { roomName == null },
maxLines = 1,
overflow = TextOverflow.Ellipsis
nameColor = ElementTheme.colors.textPrimary,
nameFontStyle = FontStyle.Italic.takeIf { roomName == null },
)
}
}
@@ -184,6 +187,7 @@ internal fun MessagesViewTopBarPreview() = ElementPreview {
roomCallState: RoomCallState = RoomCallState.Unavailable,
dmUserIdentityState: IdentityState? = null,
sharedHistoryIcon: SharedHistoryIcon = SharedHistoryIcon.NONE,
dmUserStatus: DisplayedStatus? = null,
displayThreads: Boolean = false,
) = MessagesViewTopBar(
roomName = roomName,
@@ -192,6 +196,7 @@ internal fun MessagesViewTopBarPreview() = ElementPreview {
heroes = heroes,
dmUserIdentityState = dmUserIdentityState,
sharedHistoryIcon = sharedHistoryIcon,
dmUserStatus = dmUserStatus,
onRoomDetailsClick = {},
onBackClick = {},
menuActions = {
@@ -246,5 +251,11 @@ internal fun MessagesViewTopBarPreview() = ElementPreview {
AMessagesViewTopBar(
displayThreads = true,
)
HorizontalDivider()
AMessagesViewTopBar(
roomName = "A DM with a very very very long name",
dmUserIdentityState = IdentityState.Verified,
dmUserStatus = DisplayedStatus.InCall(0L)
)
}
}