mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 16:10:00 +00:00
Display user status next to display names across rooms and profiles
This commit is contained in:
+15
-11
@@ -63,8 +63,11 @@ import io.element.android.libraries.designsystem.theme.roomListRoomName
|
|||||||
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
||||||
import io.element.android.libraries.matrix.api.notification.CallIntent
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.ui.components.InviteSenderView
|
import io.element.android.libraries.matrix.ui.components.InviteSenderView
|
||||||
|
import io.element.android.libraries.matrix.ui.components.DisplayNameWithStatus
|
||||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
|
import io.element.android.libraries.matrix.ui.model.toEmojiText
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@@ -126,7 +129,8 @@ internal fun RoomSummaryRow(
|
|||||||
NameAndTimestampRow(
|
NameAndTimestampRow(
|
||||||
name = room.name,
|
name = room.name,
|
||||||
timestamp = room.timestamp,
|
timestamp = room.timestamp,
|
||||||
isHighlighted = room.isHighlighted
|
isHighlighted = room.isHighlighted,
|
||||||
|
dmUserStatus = room.dmUserStatus,
|
||||||
)
|
)
|
||||||
MessagePreviewAndIndicatorRow(room = room, showUnreadCount = showUnreadCount)
|
MessagePreviewAndIndicatorRow(room = room, showUnreadCount = showUnreadCount)
|
||||||
}
|
}
|
||||||
@@ -142,7 +146,8 @@ internal fun RoomSummaryRow(
|
|||||||
NameAndTimestampRow(
|
NameAndTimestampRow(
|
||||||
name = room.name,
|
name = room.name,
|
||||||
timestamp = null,
|
timestamp = null,
|
||||||
isHighlighted = room.isHighlighted
|
isHighlighted = room.isHighlighted,
|
||||||
|
dmUserStatus = null,
|
||||||
)
|
)
|
||||||
if (room.canonicalAlias != null) {
|
if (room.canonicalAlias != null) {
|
||||||
Text(
|
Text(
|
||||||
@@ -218,22 +223,21 @@ private fun NameAndTimestampRow(
|
|||||||
name: String?,
|
name: String?,
|
||||||
timestamp: String?,
|
timestamp: String?,
|
||||||
isHighlighted: Boolean,
|
isHighlighted: Boolean,
|
||||||
|
dmUserStatus: DisplayedStatus?,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = spacedBy(16.dp)
|
horizontalArrangement = spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
val displayName = name?.toSafeLength(ellipsize = true) ?: stringResource(id = CommonStrings.common_no_room_name)
|
||||||
modifier = Modifier
|
DisplayNameWithStatus(
|
||||||
.weight(1f)
|
name = displayName,
|
||||||
.clipToBounds(),
|
status = dmUserStatus?.toEmojiText(),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
style = ElementTheme.typography.fontBodyLgMedium,
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
text = name?.toSafeLength(ellipsize = true) ?: stringResource(id = CommonStrings.common_no_room_name),
|
nameColor = ElementTheme.colors.roomListRoomName,
|
||||||
fontStyle = FontStyle.Italic.takeIf { name == null },
|
nameFontStyle = FontStyle.Italic.takeIf { name == null },
|
||||||
color = ElementTheme.colors.roomListRoomName,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
)
|
||||||
// Timestamp
|
// Timestamp
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
+1
@@ -76,6 +76,7 @@ class RoomListRoomSummaryFactory(
|
|||||||
}.toImmutableList(),
|
}.toImmutableList(),
|
||||||
isTombstoned = roomInfo.successorRoom != null,
|
isTombstoned = roomInfo.successorRoom != null,
|
||||||
isSpace = roomInfo.isSpace,
|
isSpace = roomInfo.isSpace,
|
||||||
|
dmUserStatus = if (roomInfo.isDm) roomInfo.heroes.firstOrNull()?.displayedStatus else null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
@@ -15,6 +15,7 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
|
|||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.notification.CallIntent
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ data class RoomListRoomSummary(
|
|||||||
val isTombstoned: Boolean,
|
val isTombstoned: Boolean,
|
||||||
val heroes: ImmutableList<AvatarData>,
|
val heroes: ImmutableList<AvatarData>,
|
||||||
val isSpace: Boolean,
|
val isSpace: Boolean,
|
||||||
|
val dmUserStatus: DisplayedStatus?,
|
||||||
) {
|
) {
|
||||||
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
||||||
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) ||
|
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) ||
|
||||||
|
|||||||
+4
-1
@@ -20,6 +20,7 @@ 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.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.notification.CallIntent
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
@@ -182,6 +183,7 @@ internal fun aRoomListRoomSummary(
|
|||||||
heroes: List<AvatarData> = emptyList(),
|
heroes: List<AvatarData> = emptyList(),
|
||||||
isTombstoned: Boolean = false,
|
isTombstoned: Boolean = false,
|
||||||
isSpace: Boolean = false,
|
isSpace: Boolean = false,
|
||||||
|
dmUserStatus: DisplayedStatus? = null,
|
||||||
) = RoomListRoomSummary(
|
) = RoomListRoomSummary(
|
||||||
id = id,
|
id = id,
|
||||||
roomId = RoomId(id),
|
roomId = RoomId(id),
|
||||||
@@ -204,5 +206,6 @@ internal fun aRoomListRoomSummary(
|
|||||||
canonicalAlias = canonicalAlias,
|
canonicalAlias = canonicalAlias,
|
||||||
heroes = heroes.toImmutableList(),
|
heroes = heroes.toImmutableList(),
|
||||||
isTombstoned = isTombstoned,
|
isTombstoned = isTombstoned,
|
||||||
isSpace = isSpace
|
isSpace = isSpace,
|
||||||
|
dmUserStatus = dmUserStatus,
|
||||||
)
|
)
|
||||||
|
|||||||
+1
@@ -319,6 +319,7 @@ class MessagesPresenter(
|
|||||||
appName = buildMeta.applicationName,
|
appName = buildMeta.applicationName,
|
||||||
pinnedMessagesBannerState = pinnedMessagesBannerState,
|
pinnedMessagesBannerState = pinnedMessagesBannerState,
|
||||||
dmUserVerificationState = dmUserVerificationState,
|
dmUserVerificationState = dmUserVerificationState,
|
||||||
|
dmUserStatus = dmRoomMember?.displayedStatus,
|
||||||
roomMemberModerationState = roomMemberModerationState,
|
roomMemberModerationState = roomMemberModerationState,
|
||||||
topBarSharedHistoryIcon = topBarSharedHistoryIcon,
|
topBarSharedHistoryIcon = topBarSharedHistoryIcon,
|
||||||
successorRoom = roomInfo.successorRoom,
|
successorRoom = roomInfo.successorRoom,
|
||||||
|
|||||||
+2
@@ -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.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
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.room.tombstone.SuccessorRoom
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
data class MessagesState(
|
data class MessagesState(
|
||||||
@@ -53,6 +54,7 @@ data class MessagesState(
|
|||||||
val appName: String,
|
val appName: String,
|
||||||
val pinnedMessagesBannerState: PinnedMessagesBannerState,
|
val pinnedMessagesBannerState: PinnedMessagesBannerState,
|
||||||
val dmUserVerificationState: IdentityState?,
|
val dmUserVerificationState: IdentityState?,
|
||||||
|
val dmUserStatus: DisplayedStatus?,
|
||||||
val roomMemberModerationState: RoomMemberModerationState,
|
val roomMemberModerationState: RoomMemberModerationState,
|
||||||
/** Type of "shared history" icon to show in the top bar. */
|
/** Type of "shared history" icon to show in the top bar. */
|
||||||
val topBarSharedHistoryIcon: SharedHistoryIcon,
|
val topBarSharedHistoryIcon: SharedHistoryIcon,
|
||||||
|
|||||||
+3
@@ -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.encryption.identity.IdentityState
|
||||||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
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.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.MessageComposerMode
|
||||||
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
|
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
|
||||||
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
|
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
|
||||||
@@ -129,6 +130,7 @@ fun aMessagesState(
|
|||||||
hasUnreadThreads = false,
|
hasUnreadThreads = false,
|
||||||
),
|
),
|
||||||
isCurrentlySharingLiveLocationInRoom: Boolean = false,
|
isCurrentlySharingLiveLocationInRoom: Boolean = false,
|
||||||
|
dmUserStatus: DisplayedStatus? = null,
|
||||||
eventSink: (MessagesEvent) -> Unit = {},
|
eventSink: (MessagesEvent) -> Unit = {},
|
||||||
) = MessagesState(
|
) = MessagesState(
|
||||||
roomId = RoomId("!id:domain"),
|
roomId = RoomId("!id:domain"),
|
||||||
@@ -159,6 +161,7 @@ fun aMessagesState(
|
|||||||
successorRoom = successorRoom,
|
successorRoom = successorRoom,
|
||||||
threads = threads,
|
threads = threads,
|
||||||
showLiveLocationShareBanner = isCurrentlySharingLiveLocationInRoom,
|
showLiveLocationShareBanner = isCurrentlySharingLiveLocationInRoom,
|
||||||
|
dmUserStatus = dmUserStatus,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+1
@@ -244,6 +244,7 @@ fun MessagesView(
|
|||||||
heroes = state.heroes,
|
heroes = state.heroes,
|
||||||
dmUserIdentityState = state.dmUserVerificationState,
|
dmUserIdentityState = state.dmUserVerificationState,
|
||||||
sharedHistoryIcon = state.topBarSharedHistoryIcon,
|
sharedHistoryIcon = state.topBarSharedHistoryIcon,
|
||||||
|
dmUserStatus = state.dmUserStatus,
|
||||||
onBackClick = { hidingKeyboard { onBackClick() } },
|
onBackClick = { hidingKeyboard { onBackClick() } },
|
||||||
onRoomDetailsClick = { hidingKeyboard { onRoomDetailsClick() } },
|
onRoomDetailsClick = { hidingKeyboard { onRoomDetailsClick() } },
|
||||||
menuActions = {
|
menuActions = {
|
||||||
|
|||||||
+13
-6
@@ -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.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
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.getAvatarData
|
||||||
|
import io.element.android.libraries.matrix.ui.model.toEmojiText
|
||||||
import io.element.android.libraries.slashcommands.api.SlashCommandSuggestion
|
import io.element.android.libraries.slashcommands.api.SlashCommandSuggestion
|
||||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
@@ -112,12 +114,16 @@ private fun SuggestionItemView(
|
|||||||
is ResolvedSuggestion.Member -> AvatarType.User
|
is ResolvedSuggestion.Member -> AvatarType.User
|
||||||
is ResolvedSuggestion.Command -> null
|
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.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.Alias -> suggestion.roomName
|
||||||
is ResolvedSuggestion.Command -> suggestion.command.command
|
is ResolvedSuggestion.Command -> suggestion.command.command
|
||||||
}
|
}
|
||||||
|
val statusEmoji = when (suggestion) {
|
||||||
|
is ResolvedSuggestion.Member -> suggestion.roomMember.displayedStatus?.toEmojiText()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
val details = when (suggestion) {
|
val details = when (suggestion) {
|
||||||
is ResolvedSuggestion.AtRoom,
|
is ResolvedSuggestion.AtRoom,
|
||||||
is ResolvedSuggestion.Member,
|
is ResolvedSuggestion.Member,
|
||||||
@@ -148,11 +154,11 @@ private fun SuggestionItemView(
|
|||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
title?.let {
|
title?.let {
|
||||||
Text(
|
DisplayNameWithStatus(
|
||||||
text = it,
|
name = title,
|
||||||
|
status = statusEmoji,
|
||||||
style = ElementTheme.typography.fontBodyLgRegular,
|
style = ElementTheme.typography.fontBodyLgRegular,
|
||||||
maxLines = 1,
|
nameColor = ElementTheme.colors.textPrimary,
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
details?.let {
|
details?.let {
|
||||||
@@ -191,6 +197,7 @@ internal fun SuggestionsPickerViewPreview() {
|
|||||||
role = RoomMember.Role.User,
|
role = RoomMember.Role.User,
|
||||||
membershipChangeReason = null,
|
membershipChangeReason = null,
|
||||||
isServiceMember = false,
|
isServiceMember = false,
|
||||||
|
displayedStatus = null,
|
||||||
)
|
)
|
||||||
val anAlias = remember { RoomAlias("#room:domain.org") }
|
val anAlias = remember { RoomAlias("#room:domain.org") }
|
||||||
SuggestionsPickerView(
|
SuggestionsPickerView(
|
||||||
|
|||||||
+6
-1
@@ -383,7 +383,12 @@ fun aThreadListItem(
|
|||||||
fun aThreadListItemEvent(
|
fun aThreadListItemEvent(
|
||||||
threadId: ThreadId = ThreadId("\$a-thread-id"),
|
threadId: ThreadId = ThreadId("\$a-thread-id"),
|
||||||
senderId: UserId = UserId("@a-user-id:server"),
|
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,
|
isOwn: Boolean = false,
|
||||||
content: EventContent = MessageContent(
|
content: EventContent = MessageContent(
|
||||||
body = "Hello world!",
|
body = "Hello world!",
|
||||||
|
|||||||
+1
@@ -166,6 +166,7 @@ internal fun MessageShieldViewPreview() {
|
|||||||
displayName = USER_NAME_ALICE,
|
displayName = USER_NAME_ALICE,
|
||||||
displayNameAmbiguous = false,
|
displayNameAmbiguous = false,
|
||||||
avatarUrl = null,
|
avatarUrl = null,
|
||||||
|
displayedStatus = null,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
+2
@@ -962,6 +962,7 @@ internal fun TimelineItemEventRowWithThreadSummaryPreview() = ElementPreview {
|
|||||||
displayName = USER_NAME_ALICE,
|
displayName = USER_NAME_ALICE,
|
||||||
avatarUrl = null,
|
avatarUrl = null,
|
||||||
displayNameAmbiguous = false,
|
displayNameAmbiguous = false,
|
||||||
|
displayedStatus = null,
|
||||||
),
|
),
|
||||||
timestamp = 0L,
|
timestamp = 0L,
|
||||||
)
|
)
|
||||||
@@ -1140,6 +1141,7 @@ internal fun ThreadSummaryViewPreview() {
|
|||||||
displayName = USER_NAME_ALICE,
|
displayName = USER_NAME_ALICE,
|
||||||
avatarUrl = null,
|
avatarUrl = null,
|
||||||
displayNameAmbiguous = true,
|
displayNameAmbiguous = true,
|
||||||
|
displayedStatus = null,
|
||||||
),
|
),
|
||||||
timestamp = 0L,
|
timestamp = 0L,
|
||||||
)
|
)
|
||||||
|
|||||||
+20
-9
@@ -26,7 +26,6 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.semantics.heading
|
import androidx.compose.ui.semantics.heading
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
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.preview.ROOM_NAME
|
||||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
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.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
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.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.components.aMatrixUserList
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
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 io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
@@ -65,6 +66,7 @@ internal fun MessagesViewTopBar(
|
|||||||
heroes: ImmutableList<AvatarData>,
|
heroes: ImmutableList<AvatarData>,
|
||||||
dmUserIdentityState: IdentityState?,
|
dmUserIdentityState: IdentityState?,
|
||||||
sharedHistoryIcon: SharedHistoryIcon,
|
sharedHistoryIcon: SharedHistoryIcon,
|
||||||
|
dmUserStatus: DisplayedStatus?,
|
||||||
onRoomDetailsClick: () -> Unit,
|
onRoomDetailsClick: () -> Unit,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@@ -91,6 +93,7 @@ internal fun MessagesViewTopBar(
|
|||||||
roomAvatar = roomAvatar,
|
roomAvatar = roomAvatar,
|
||||||
isTombstoned = isTombstoned,
|
isTombstoned = isTombstoned,
|
||||||
heroes = heroes,
|
heroes = heroes,
|
||||||
|
dmUserStatus = dmUserStatus,
|
||||||
modifier = titleModifier
|
modifier = titleModifier
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -144,6 +147,7 @@ private fun RoomAvatarAndNameRow(
|
|||||||
roomAvatar: AvatarData,
|
roomAvatar: AvatarData,
|
||||||
heroes: ImmutableList<AvatarData>,
|
heroes: ImmutableList<AvatarData>,
|
||||||
isTombstoned: Boolean,
|
isTombstoned: Boolean,
|
||||||
|
dmUserStatus: DisplayedStatus?,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@@ -157,14 +161,13 @@ private fun RoomAvatarAndNameRow(
|
|||||||
isTombstoned = isTombstoned,
|
isTombstoned = isTombstoned,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
Text(
|
DisplayNameWithStatus(
|
||||||
modifier = Modifier
|
name = roomName ?: stringResource(CommonStrings.common_no_room_name),
|
||||||
.padding(start = 8.dp),
|
status = dmUserStatus?.toEmojiText(),
|
||||||
text = roomName ?: stringResource(CommonStrings.common_no_room_name),
|
modifier = Modifier.padding(start = 8.dp),
|
||||||
style = ElementTheme.typography.fontBodyLgMedium,
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
fontStyle = FontStyle.Italic.takeIf { roomName == null },
|
nameColor = ElementTheme.colors.textPrimary,
|
||||||
maxLines = 1,
|
nameFontStyle = FontStyle.Italic.takeIf { roomName == null },
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,6 +187,7 @@ internal fun MessagesViewTopBarPreview() = ElementPreview {
|
|||||||
roomCallState: RoomCallState = RoomCallState.Unavailable,
|
roomCallState: RoomCallState = RoomCallState.Unavailable,
|
||||||
dmUserIdentityState: IdentityState? = null,
|
dmUserIdentityState: IdentityState? = null,
|
||||||
sharedHistoryIcon: SharedHistoryIcon = SharedHistoryIcon.NONE,
|
sharedHistoryIcon: SharedHistoryIcon = SharedHistoryIcon.NONE,
|
||||||
|
dmUserStatus: DisplayedStatus? = null,
|
||||||
displayThreads: Boolean = false,
|
displayThreads: Boolean = false,
|
||||||
) = MessagesViewTopBar(
|
) = MessagesViewTopBar(
|
||||||
roomName = roomName,
|
roomName = roomName,
|
||||||
@@ -192,6 +196,7 @@ internal fun MessagesViewTopBarPreview() = ElementPreview {
|
|||||||
heroes = heroes,
|
heroes = heroes,
|
||||||
dmUserIdentityState = dmUserIdentityState,
|
dmUserIdentityState = dmUserIdentityState,
|
||||||
sharedHistoryIcon = sharedHistoryIcon,
|
sharedHistoryIcon = sharedHistoryIcon,
|
||||||
|
dmUserStatus = dmUserStatus,
|
||||||
onRoomDetailsClick = {},
|
onRoomDetailsClick = {},
|
||||||
onBackClick = {},
|
onBackClick = {},
|
||||||
menuActions = {
|
menuActions = {
|
||||||
@@ -246,5 +251,11 @@ internal fun MessagesViewTopBarPreview() = ElementPreview {
|
|||||||
AMessagesViewTopBar(
|
AMessagesViewTopBar(
|
||||||
displayThreads = true,
|
displayThreads = true,
|
||||||
)
|
)
|
||||||
|
HorizontalDivider()
|
||||||
|
AMessagesViewTopBar(
|
||||||
|
roomName = "A DM with a very very very long name",
|
||||||
|
dmUserIdentityState = IdentityState.Verified,
|
||||||
|
dmUserStatus = DisplayedStatus.InCall(0L)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
|||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
||||||
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
@@ -75,6 +76,7 @@ fun aDmRoomMember(
|
|||||||
role: RoomMember.Role = RoomMember.Role.User,
|
role: RoomMember.Role = RoomMember.Role.User,
|
||||||
membershipChangeReason: String? = null,
|
membershipChangeReason: String? = null,
|
||||||
isServiceMember: Boolean = false,
|
isServiceMember: Boolean = false,
|
||||||
|
displayedStatus: DisplayedStatus? = null,
|
||||||
) = RoomMember(
|
) = RoomMember(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
@@ -86,6 +88,7 @@ fun aDmRoomMember(
|
|||||||
role = role,
|
role = role,
|
||||||
membershipChangeReason = membershipChangeReason,
|
membershipChangeReason = membershipChangeReason,
|
||||||
isServiceMember = isServiceMember,
|
isServiceMember = isServiceMember,
|
||||||
|
displayedStatus = displayedStatus,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aRoomDetailsState(
|
fun aRoomDetailsState(
|
||||||
@@ -178,12 +181,13 @@ fun aDmRoomDetailsState(
|
|||||||
roomName: String = "Daniel",
|
roomName: String = "Daniel",
|
||||||
isEncrypted: Boolean = true,
|
isEncrypted: Boolean = true,
|
||||||
dmRoomMemberVerificationState: UserProfileVerificationState = UserProfileVerificationState.UNKNOWN,
|
dmRoomMemberVerificationState: UserProfileVerificationState = UserProfileVerificationState.UNKNOWN,
|
||||||
|
displayedStatus: DisplayedStatus? = null,
|
||||||
) = aRoomDetailsState(
|
) = aRoomDetailsState(
|
||||||
roomName = roomName,
|
roomName = roomName,
|
||||||
isPublic = false,
|
isPublic = false,
|
||||||
isEncrypted = isEncrypted,
|
isEncrypted = isEncrypted,
|
||||||
canInvite = true,
|
canInvite = true,
|
||||||
roomType = RoomDetailsType.Dm(otherMember = aDmRoomMember(isIgnored = isDmMemberIgnored)),
|
roomType = RoomDetailsType.Dm(otherMember = aDmRoomMember(isIgnored = isDmMemberIgnored, displayedStatus = displayedStatus)),
|
||||||
dmOtherMemberDetailsState = aUserProfileState(
|
dmOtherMemberDetailsState = aUserProfileState(
|
||||||
isBlocked = AsyncData.Success(isDmMemberIgnored),
|
isBlocked = AsyncData.Success(isDmMemberIgnored),
|
||||||
verificationState = dmRoomMemberVerificationState,
|
verificationState = dmRoomMemberVerificationState,
|
||||||
|
|||||||
+36
-25
@@ -37,6 +37,7 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import im.vector.app.features.analytics.plan.Interaction
|
import im.vector.app.features.analytics.plan.Interaction
|
||||||
@@ -88,6 +89,7 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
|||||||
import io.element.android.libraries.matrix.api.room.getBestName
|
import io.element.android.libraries.matrix.api.room.getBestName
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
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.getAvatarData
|
||||||
|
import io.element.android.libraries.matrix.ui.model.toText
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.testtags.testTag
|
import io.element.android.libraries.testtags.testTag
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
@@ -489,11 +491,23 @@ private fun RoomHeaderSection(
|
|||||||
}
|
}
|
||||||
.testTag(TestTags.roomDetailAvatar)
|
.testTag(TestTags.roomDetailAvatar)
|
||||||
)
|
)
|
||||||
TitleAndSubtitle(
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
title = roomName,
|
Text(
|
||||||
subtitle = roomAlias?.value,
|
text = roomName,
|
||||||
onSubtitleClick = onSubtitleClick,
|
style = ElementTheme.typography.fontHeadingLgBold,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
|
if (roomAlias != null) {
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.niceClickable { onSubtitleClick(roomAlias.value) },
|
||||||
|
text = roomAlias.value,
|
||||||
|
style = ElementTheme.typography.fontBodyLgRegular,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,37 +545,34 @@ private fun DmHeaderSection(
|
|||||||
}
|
}
|
||||||
.testTag(TestTags.roomDetailAvatar)
|
.testTag(TestTags.roomDetailAvatar)
|
||||||
)
|
)
|
||||||
TitleAndSubtitle(
|
|
||||||
title = roomName,
|
|
||||||
subtitle = otherMember.userId.value,
|
|
||||||
onSubtitleClick = onSubtitleClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun TitleAndSubtitle(
|
|
||||||
title: String,
|
|
||||||
subtitle: String?,
|
|
||||||
onSubtitleClick: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = roomName,
|
||||||
style = ElementTheme.typography.fontHeadingLgBold,
|
style = ElementTheme.typography.fontHeadingLgBold,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
if (subtitle != null) {
|
val userStatus = otherMember.displayedStatus?.toText()
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
if(userStatus != null){
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.niceClickable { onSubtitleClick(subtitle) },
|
text = userStatus,
|
||||||
text = subtitle,
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
maxLines = 1,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.niceClickable { onSubtitleClick(otherMember.userId.value) },
|
||||||
|
text = otherMember.userId.value,
|
||||||
style = ElementTheme.typography.fontBodyLgRegular,
|
style = ElementTheme.typography.fontBodyLgRegular,
|
||||||
color = ElementTheme.colors.textSecondary,
|
color = ElementTheme.colors.textSecondary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
}
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
@@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.core.UserId
|
|||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMemberListState> {
|
internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMemberListState> {
|
||||||
@@ -129,6 +130,7 @@ fun aRoomMember(
|
|||||||
role: RoomMember.Role = RoomMember.Role.User,
|
role: RoomMember.Role = RoomMember.Role.User,
|
||||||
membershipChangeReason: String? = null,
|
membershipChangeReason: String? = null,
|
||||||
isServiceMember: Boolean = false,
|
isServiceMember: Boolean = false,
|
||||||
|
displayedStatus: DisplayedStatus? = null,
|
||||||
) = RoomMember(
|
) = RoomMember(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
@@ -140,6 +142,7 @@ fun aRoomMember(
|
|||||||
role = role,
|
role = role,
|
||||||
membershipChangeReason = membershipChangeReason,
|
membershipChangeReason = membershipChangeReason,
|
||||||
isServiceMember = isServiceMember,
|
isServiceMember = isServiceMember,
|
||||||
|
displayedStatus = displayedStatus,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aRoomMemberList() = persistentListOf(
|
fun aRoomMemberList() = persistentListOf(
|
||||||
|
|||||||
+1
@@ -128,6 +128,7 @@ class RoomMemberDetailsPresenter(
|
|||||||
return userProfileState.copy(
|
return userProfileState.copy(
|
||||||
userName = roomUserName ?: userProfileState.userName,
|
userName = roomUserName ?: userProfileState.userName,
|
||||||
avatarUrl = roomUserAvatar ?: userProfileState.avatarUrl,
|
avatarUrl = roomUserAvatar ?: userProfileState.avatarUrl,
|
||||||
|
displayedStatus = roomMember?.displayedStatus ?: userProfileState.displayedStatus,
|
||||||
verificationState = verificationState,
|
verificationState = verificationState,
|
||||||
snackbarMessage = snackbarMessage,
|
snackbarMessage = snackbarMessage,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
|
|||||||
+2
@@ -16,6 +16,7 @@ import io.element.android.features.roommembermoderation.api.RoomMemberModeration
|
|||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
|
import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
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
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
@@ -82,6 +83,7 @@ fun anAlice() = MatrixUser(
|
|||||||
userId = UserId(value = "@alice:server.org"),
|
userId = UserId(value = "@alice:server.org"),
|
||||||
displayName = USER_NAME_ALICE,
|
displayName = USER_NAME_ALICE,
|
||||||
avatarUrl = null,
|
avatarUrl = null,
|
||||||
|
displayedStatus = DisplayedStatus.InCall(0L),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aRoomMembersModerationState(
|
fun aRoomMembersModerationState(
|
||||||
|
|||||||
+16
-1
@@ -57,6 +57,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.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
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.getBestName
|
||||||
|
import io.element.android.libraries.matrix.ui.model.toText
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -264,9 +265,23 @@ private fun RoomMemberActionsBottomSheet(
|
|||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
|
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
val userStatus = user.displayedStatus?.toText()
|
||||||
|
if(userStatus != null){
|
||||||
|
Text(
|
||||||
|
text = userStatus,
|
||||||
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
maxLines = 1,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
// Show user ID only if it's different from the display name
|
// Show user ID only if it's different from the display name
|
||||||
if (bestName != user.userId.value) {
|
if (bestName != user.userId.value) {
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
+2
@@ -13,6 +13,7 @@ import io.element.android.libraries.architecture.AsyncData
|
|||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
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.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
|
|
||||||
data class UserProfileState(
|
data class UserProfileState(
|
||||||
val userId: UserId,
|
val userId: UserId,
|
||||||
@@ -26,6 +27,7 @@ data class UserProfileState(
|
|||||||
val dmRoomId: RoomId?,
|
val dmRoomId: RoomId?,
|
||||||
val canCall: Boolean,
|
val canCall: Boolean,
|
||||||
val snackbarMessage: SnackbarMessage?,
|
val snackbarMessage: SnackbarMessage?,
|
||||||
|
val displayedStatus: DisplayedStatus?,
|
||||||
val eventSink: (UserProfileEvents) -> Unit
|
val eventSink: (UserProfileEvents) -> Unit
|
||||||
) {
|
) {
|
||||||
enum class ConfirmationDialog {
|
enum class ConfirmationDialog {
|
||||||
|
|||||||
+1
@@ -145,6 +145,7 @@ class UserProfilePresenter(
|
|||||||
userId = userId,
|
userId = userId,
|
||||||
userName = userProfile?.displayName,
|
userName = userProfile?.displayName,
|
||||||
avatarUrl = userProfile?.avatarUrl,
|
avatarUrl = userProfile?.avatarUrl,
|
||||||
|
displayedStatus = userProfile?.displayedStatus,
|
||||||
isBlocked = isBlocked.value,
|
isBlocked = isBlocked.value,
|
||||||
verificationState = UserProfileVerificationState.UNKNOWN,
|
verificationState = UserProfileVerificationState.UNKNOWN,
|
||||||
startDmActionState = startDmActionState.value,
|
startDmActionState = startDmActionState.value,
|
||||||
|
|||||||
+16
-1
@@ -42,6 +42,8 @@ import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
|||||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
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.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
|
import io.element.android.libraries.matrix.ui.model.toText
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.testtags.testTag
|
import io.element.android.libraries.testtags.testTag
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
@@ -56,6 +58,7 @@ fun UserProfileHeaderSection(
|
|||||||
openAvatarPreview: (url: String) -> Unit,
|
openAvatarPreview: (url: String) -> Unit,
|
||||||
onUserIdClick: () -> Unit,
|
onUserIdClick: () -> Unit,
|
||||||
withdrawVerificationClick: () -> Unit,
|
withdrawVerificationClick: () -> Unit,
|
||||||
|
displayedStatus: DisplayedStatus?,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
@@ -90,7 +93,16 @@ fun UserProfileHeaderSection(
|
|||||||
style = ElementTheme.typography.fontHeadingLgBold,
|
style = ElementTheme.typography.fontHeadingLgBold,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
}
|
||||||
|
displayedStatus?.let { status ->
|
||||||
|
Text(
|
||||||
|
text = status.toText(),
|
||||||
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.niceClickable { onUserIdClick() },
|
modifier = Modifier.niceClickable { onUserIdClick() },
|
||||||
@@ -99,6 +111,7 @@ fun UserProfileHeaderSection(
|
|||||||
color = ElementTheme.colors.textSecondary,
|
color = ElementTheme.colors.textSecondary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
when (verificationState) {
|
when (verificationState) {
|
||||||
UserProfileVerificationState.UNKNOWN, UserProfileVerificationState.UNVERIFIED -> Unit
|
UserProfileVerificationState.UNKNOWN, UserProfileVerificationState.UNVERIFIED -> Unit
|
||||||
UserProfileVerificationState.VERIFIED -> {
|
UserProfileVerificationState.VERIFIED -> {
|
||||||
@@ -143,6 +156,7 @@ internal fun UserProfileHeaderSectionPreview() = ElementPreview {
|
|||||||
userId = UserId("@alice:example.com"),
|
userId = UserId("@alice:example.com"),
|
||||||
userName = USER_NAME_ALICE,
|
userName = USER_NAME_ALICE,
|
||||||
verificationState = UserProfileVerificationState.VERIFIED,
|
verificationState = UserProfileVerificationState.VERIFIED,
|
||||||
|
displayedStatus = DisplayedStatus.InCall(0L),
|
||||||
openAvatarPreview = {},
|
openAvatarPreview = {},
|
||||||
onUserIdClick = {},
|
onUserIdClick = {},
|
||||||
withdrawVerificationClick = {},
|
withdrawVerificationClick = {},
|
||||||
@@ -157,6 +171,7 @@ internal fun UserProfileHeaderSectionWithVerificationViolationPreview() = Elemen
|
|||||||
userId = UserId("@alice:example.com"),
|
userId = UserId("@alice:example.com"),
|
||||||
userName = USER_NAME_ALICE,
|
userName = USER_NAME_ALICE,
|
||||||
verificationState = UserProfileVerificationState.VERIFICATION_VIOLATION,
|
verificationState = UserProfileVerificationState.VERIFICATION_VIOLATION,
|
||||||
|
displayedStatus = DisplayedStatus.InCall(0L),
|
||||||
openAvatarPreview = {},
|
openAvatarPreview = {},
|
||||||
onUserIdClick = {},
|
onUserIdClick = {},
|
||||||
withdrawVerificationClick = {},
|
withdrawVerificationClick = {},
|
||||||
|
|||||||
+3
@@ -18,6 +18,7 @@ import io.element.android.libraries.architecture.AsyncData
|
|||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
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.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||||
|
|
||||||
open class UserProfileStateProvider : PreviewParameterProvider<UserProfileState> {
|
open class UserProfileStateProvider : PreviewParameterProvider<UserProfileState> {
|
||||||
@@ -48,6 +49,7 @@ fun aUserProfileState(
|
|||||||
dmRoomId: RoomId? = null,
|
dmRoomId: RoomId? = null,
|
||||||
canCall: Boolean = false,
|
canCall: Boolean = false,
|
||||||
snackbarMessage: SnackbarMessage? = null,
|
snackbarMessage: SnackbarMessage? = null,
|
||||||
|
displayedStatus: DisplayedStatus? = null,
|
||||||
eventSink: (UserProfileEvents) -> Unit = {},
|
eventSink: (UserProfileEvents) -> Unit = {},
|
||||||
) = UserProfileState(
|
) = UserProfileState(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
@@ -61,5 +63,6 @@ fun aUserProfileState(
|
|||||||
dmRoomId = dmRoomId,
|
dmRoomId = dmRoomId,
|
||||||
canCall = canCall,
|
canCall = canCall,
|
||||||
snackbarMessage = snackbarMessage,
|
snackbarMessage = snackbarMessage,
|
||||||
|
displayedStatus = displayedStatus,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|||||||
+1
@@ -78,6 +78,7 @@ fun UserProfileView(
|
|||||||
userId = state.userId,
|
userId = state.userId,
|
||||||
userName = state.userName,
|
userName = state.userName,
|
||||||
verificationState = state.verificationState,
|
verificationState = state.verificationState,
|
||||||
|
displayedStatus = state.displayedStatus,
|
||||||
openAvatarPreview = { avatarUrl ->
|
openAvatarPreview = { avatarUrl ->
|
||||||
openAvatarPreview(state.userName ?: state.userId.value, avatarUrl)
|
openAvatarPreview(state.userName ?: state.userId.value, avatarUrl)
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ fun MatrixBadgeRowMolecule(
|
|||||||
) {
|
) {
|
||||||
FlowRow(
|
FlowRow(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(start = 16.dp, end = 16.dp, top = 8.dp),
|
.padding(start = 16.dp, end = 16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
|
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
|
|||||||
+2
@@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.api.room
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
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
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
|
||||||
data class RoomMember(
|
data class RoomMember(
|
||||||
@@ -23,6 +24,7 @@ data class RoomMember(
|
|||||||
val role: Role,
|
val role: Role,
|
||||||
val membershipChangeReason: String?,
|
val membershipChangeReason: String?,
|
||||||
val isServiceMember: Boolean,
|
val isServiceMember: Boolean,
|
||||||
|
val displayedStatus: DisplayedStatus?,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Role of the RoomMember, based on its [powerLevel].
|
* Role of the RoomMember, based on its [powerLevel].
|
||||||
|
|||||||
+3
-1
@@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.api.timeline.item.event
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed interface ProfileDetails {
|
sealed interface ProfileDetails {
|
||||||
@@ -20,7 +21,8 @@ sealed interface ProfileDetails {
|
|||||||
data class Ready(
|
data class Ready(
|
||||||
val displayName: String?,
|
val displayName: String?,
|
||||||
val displayNameAmbiguous: Boolean,
|
val displayNameAmbiguous: Boolean,
|
||||||
val avatarUrl: String?
|
val avatarUrl: String?,
|
||||||
|
val displayedStatus: DisplayedStatus?,
|
||||||
) : ProfileDetails
|
) : ProfileDetails
|
||||||
|
|
||||||
data class Error(
|
data class Error(
|
||||||
|
|||||||
+3
@@ -38,6 +38,7 @@ class ProfileTimelineDetailsTest {
|
|||||||
displayName = null,
|
displayName = null,
|
||||||
displayNameAmbiguous = false,
|
displayNameAmbiguous = false,
|
||||||
avatarUrl = null,
|
avatarUrl = null,
|
||||||
|
displayedStatus = null,
|
||||||
).getDisambiguatedDisplayName(aUserId)
|
).getDisambiguatedDisplayName(aUserId)
|
||||||
).isEqualTo(A_USER_ID)
|
).isEqualTo(A_USER_ID)
|
||||||
}
|
}
|
||||||
@@ -49,6 +50,7 @@ class ProfileTimelineDetailsTest {
|
|||||||
displayName = "Alice",
|
displayName = "Alice",
|
||||||
displayNameAmbiguous = false,
|
displayNameAmbiguous = false,
|
||||||
avatarUrl = null,
|
avatarUrl = null,
|
||||||
|
displayedStatus = null,
|
||||||
).getDisambiguatedDisplayName(aUserId)
|
).getDisambiguatedDisplayName(aUserId)
|
||||||
).isEqualTo("Alice")
|
).isEqualTo("Alice")
|
||||||
}
|
}
|
||||||
@@ -60,6 +62,7 @@ class ProfileTimelineDetailsTest {
|
|||||||
displayName = "Alice",
|
displayName = "Alice",
|
||||||
displayNameAmbiguous = true,
|
displayNameAmbiguous = true,
|
||||||
avatarUrl = null,
|
avatarUrl = null,
|
||||||
|
displayedStatus = null,
|
||||||
).getDisambiguatedDisplayName(aUserId)
|
).getDisambiguatedDisplayName(aUserId)
|
||||||
).isEqualTo("Alice ($A_USER_ID)")
|
).isEqualTo("Alice ($A_USER_ID)")
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -30,6 +30,8 @@ object RoomMemberMapper {
|
|||||||
role = mapRole(roomMember.suggestedRoleForPowerLevel, powerLevel),
|
role = mapRole(roomMember.suggestedRoleForPowerLevel, powerLevel),
|
||||||
membershipChangeReason = roomMember.membershipChangeReason,
|
membershipChangeReason = roomMember.membershipChangeReason,
|
||||||
isServiceMember = roomMember.isServiceMember,
|
isServiceMember = roomMember.isServiceMember,
|
||||||
|
//TODO map this value when available
|
||||||
|
displayedStatus = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -74,7 +74,9 @@ fun RustProfileDetails.map(): ProfileDetails {
|
|||||||
is RustProfileDetails.Ready -> ProfileDetails.Ready(
|
is RustProfileDetails.Ready -> ProfileDetails.Ready(
|
||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
displayNameAmbiguous = displayNameAmbiguous,
|
displayNameAmbiguous = displayNameAmbiguous,
|
||||||
avatarUrl = avatarUrl
|
avatarUrl = avatarUrl,
|
||||||
|
//TODO map this value when available
|
||||||
|
displayedStatus = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -33,6 +33,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.SendHandlePro
|
|||||||
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
|
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemDebugInfoProvider
|
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemDebugInfoProvider
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||||
@@ -84,11 +85,13 @@ fun anEventTimelineItem(
|
|||||||
fun aProfileDetails(
|
fun aProfileDetails(
|
||||||
displayName: String? = A_USER_NAME,
|
displayName: String? = A_USER_NAME,
|
||||||
displayNameAmbiguous: Boolean = false,
|
displayNameAmbiguous: Boolean = false,
|
||||||
avatarUrl: String? = null
|
avatarUrl: String? = null,
|
||||||
|
displayedStatus: DisplayedStatus? = null,
|
||||||
): ProfileDetails = ProfileDetails.Ready(
|
): ProfileDetails = ProfileDetails.Ready(
|
||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
displayNameAmbiguous = displayNameAmbiguous,
|
displayNameAmbiguous = displayNameAmbiguous,
|
||||||
avatarUrl = avatarUrl,
|
avatarUrl = avatarUrl,
|
||||||
|
displayedStatus = displayedStatus,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aProfileChangeMessageContent(
|
fun aProfileChangeMessageContent(
|
||||||
|
|||||||
+2
-1
@@ -19,6 +19,7 @@ import androidx.compose.runtime.Immutable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
@@ -50,7 +51,7 @@ fun CheckableUserRow(
|
|||||||
UserRow(
|
UserRow(
|
||||||
modifier = rowModifier,
|
modifier = rowModifier,
|
||||||
avatarData = data.avatarData,
|
avatarData = data.avatarData,
|
||||||
name = data.name,
|
name = AnnotatedString(data.name),
|
||||||
subtext = data.subtext,
|
subtext = data.subtext,
|
||||||
enabled = enabled,
|
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.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
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.getBestName
|
||||||
|
import io.element.android.libraries.matrix.ui.model.toEmojiText
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MatrixUserHeader(
|
fun MatrixUserHeader(
|
||||||
@@ -58,13 +59,11 @@ fun MatrixUserHeader(
|
|||||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||||
) {
|
) {
|
||||||
// Name
|
// Name
|
||||||
Text(
|
DisplayNameWithStatus(
|
||||||
modifier = Modifier.clipToBounds(),
|
name = matrixUser.getBestName(),
|
||||||
text = matrixUser.getBestName(),
|
status = matrixUser.displayedStatus?.toEmojiText(),
|
||||||
maxLines = 1,
|
nameColor = ElementTheme.colors.textPrimary,
|
||||||
style = ElementTheme.typography.fontHeadingMdRegular,
|
style = ElementTheme.typography.fontHeadingMdRegular
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
color = ElementTheme.colors.textPrimary,
|
|
||||||
)
|
)
|
||||||
// Id
|
// Id
|
||||||
if (matrixUser.displayName.isNullOrEmpty().not()) {
|
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_VICTOR
|
||||||
import io.element.android.libraries.designsystem.preview.USER_NAME_WALTER
|
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.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
|
||||||
open class MatrixUserProvider : PreviewParameterProvider<MatrixUser> {
|
open class MatrixUserProvider : PreviewParameterProvider<MatrixUser> {
|
||||||
@@ -28,6 +29,7 @@ open class MatrixUserProvider : PreviewParameterProvider<MatrixUser> {
|
|||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aMatrixUser(),
|
aMatrixUser(),
|
||||||
aMatrixUser(displayName = null),
|
aMatrixUser(displayName = null),
|
||||||
|
aMatrixUser(displayedStatus = DisplayedStatus.InCall(0L) ),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,10 +45,12 @@ fun aMatrixUser(
|
|||||||
id: String? = null,
|
id: String? = null,
|
||||||
displayName: String? = USER_NAME_ALICE,
|
displayName: String? = USER_NAME_ALICE,
|
||||||
avatarUrl: String? = null,
|
avatarUrl: String? = null,
|
||||||
|
displayedStatus: DisplayedStatus? = null,
|
||||||
) = MatrixUser(
|
) = MatrixUser(
|
||||||
userId = UserId(id ?: "@${displayName?.lowercase()?.replace(" ", "_") ?: "id"}:server.org"),
|
userId = UserId(id ?: "@${displayName?.lowercase()?.replace(" ", "_") ?: "id"}:server.org"),
|
||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
avatarUrl = avatarUrl,
|
avatarUrl = avatarUrl,
|
||||||
|
displayedStatus = displayedStatus,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aMatrixUserList() = listOf(
|
fun aMatrixUserList() = listOf(
|
||||||
|
|||||||
+9
@@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.ui.components
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
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.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
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.getBestName
|
||||||
|
import io.element.android.libraries.matrix.ui.model.toEmojiText
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MatrixUserRow(
|
fun MatrixUserRow(
|
||||||
@@ -29,7 +31,14 @@ fun MatrixUserRow(
|
|||||||
trailingContent: @Composable (() -> Unit)? = null,
|
trailingContent: @Composable (() -> Unit)? = null,
|
||||||
) = UserRow(
|
) = UserRow(
|
||||||
avatarData = matrixUser.getAvatarData(avatarSize),
|
avatarData = matrixUser.getAvatarData(avatarSize),
|
||||||
|
nameContent = {
|
||||||
|
DisplayNameWithStatus(
|
||||||
name = matrixUser.getBestName(),
|
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,
|
subtext = if (matrixUser.displayName.isNullOrEmpty()) null else matrixUser.userId.value,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
verticalSpaceWidth = verticalSpaceWidth,
|
verticalSpaceWidth = verticalSpaceWidth,
|
||||||
|
|||||||
+32
-9
@@ -18,6 +18,7 @@ import androidx.compose.runtime.Composable
|
|||||||
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.clipToBounds
|
import androidx.compose.ui.draw.clipToBounds
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
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
|
@Composable
|
||||||
internal fun UserRow(
|
internal fun UserRow(
|
||||||
avatarData: AvatarData,
|
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?,
|
subtext: String?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
@@ -52,14 +82,7 @@ internal fun UserRow(
|
|||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
) {
|
) {
|
||||||
// Name
|
// Name
|
||||||
Text(
|
nameContent()
|
||||||
modifier = Modifier.clipToBounds(),
|
|
||||||
text = name,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
color = if (enabled) ElementTheme.colors.textPrimary else ElementTheme.colors.textDisabled,
|
|
||||||
style = ElementTheme.typography.fontBodyLgRegular,
|
|
||||||
)
|
|
||||||
// Id
|
// Id
|
||||||
subtext?.let {
|
subtext?.let {
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
+6
@@ -13,6 +13,7 @@ import dev.zacsweers.metro.SingleIn
|
|||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
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.RoomMember
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
@@ -31,4 +32,9 @@ class RoomMemberProfilesCache {
|
|||||||
fun getDisplayName(userId: UserId): String? {
|
fun getDisplayName(userId: UserId): String? {
|
||||||
return cache.value[userId]?.disambiguatedDisplayName
|
return cache.value[userId]?.disambiguatedDisplayName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDisplayedStatus(userId: UserId): DisplayedStatus? {
|
||||||
|
return cache.value[userId]?.displayedStatus
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -167,4 +167,5 @@ fun aProfileDetailsReady(
|
|||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
displayNameAmbiguous = displayNameAmbiguous,
|
displayNameAmbiguous = displayNameAmbiguous,
|
||||||
avatarUrl = avatarUrl,
|
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.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
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.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
|
// https://www.figma.com/file/Ni6Ii8YKtmXCKYNE90cC67/Timeline-(new)?type=design&node-id=917-80169&mode=design&t=A0CJCBbMqR8NOwUQ-0
|
||||||
@Composable
|
@Composable
|
||||||
@@ -54,6 +55,19 @@ fun SenderName(
|
|||||||
SecondaryText(text = senderId.value, mode = senderNameMode)
|
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.ActionList,
|
||||||
SenderNameMode.Reply -> Modifier
|
SenderNameMode.Reply -> Modifier
|
||||||
}
|
}
|
||||||
|
|
||||||
val color = when (mode) {
|
val color = when (mode) {
|
||||||
is SenderNameMode.Timeline -> mode.mainColor
|
is SenderNameMode.Timeline -> mode.mainColor
|
||||||
SenderNameMode.ActionList,
|
SenderNameMode.ActionList,
|
||||||
SenderNameMode.Reply -> ElementTheme.colors.textPrimary
|
SenderNameMode.Reply -> ElementTheme.colors.textPrimary
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
modifier = modifier.clipToBounds(),
|
modifier = modifier
|
||||||
|
.clipToBounds()
|
||||||
|
.weight(1f, fill = false),
|
||||||
text = text,
|
text = text,
|
||||||
style = style,
|
style = style,
|
||||||
color = color,
|
color = color,
|
||||||
|
|||||||
+4
-1
@@ -12,6 +12,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
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.timeline.item.event.ProfileDetails
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
|
|
||||||
data class SenderNameData(
|
data class SenderNameData(
|
||||||
val userId: UserId,
|
val userId: UserId,
|
||||||
@@ -47,12 +48,14 @@ open class SenderNameDataProvider : PreviewParameterProvider<SenderNameData> {
|
|||||||
private fun aSenderNameData(
|
private fun aSenderNameData(
|
||||||
senderNameMode: SenderNameMode,
|
senderNameMode: SenderNameMode,
|
||||||
displayNameAmbiguous: Boolean = false,
|
displayNameAmbiguous: Boolean = false,
|
||||||
|
displayedStatus: DisplayedStatus? = null,
|
||||||
) = SenderNameData(
|
) = SenderNameData(
|
||||||
userId = UserId("@alice:${senderNameMode.javaClass.simpleName.lowercase()}"),
|
userId = UserId("@alice:${senderNameMode.javaClass.simpleName.lowercase()}"),
|
||||||
profileDetails = ProfileDetails.Ready(
|
profileDetails = ProfileDetails.Ready(
|
||||||
displayName = "Alice ${senderNameMode.javaClass.simpleName}",
|
displayName = "Alice ${senderNameMode.javaClass.simpleName}",
|
||||||
displayNameAmbiguous = displayNameAmbiguous,
|
displayNameAmbiguous = displayNameAmbiguous,
|
||||||
avatarUrl = null
|
avatarUrl = null,
|
||||||
|
displayedStatus = displayedStatus,
|
||||||
),
|
),
|
||||||
senderNameMode = senderNameMode,
|
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>
|
||||||
+3
@@ -21,6 +21,7 @@ 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.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
|
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
fun aRoomMember(
|
fun aRoomMember(
|
||||||
@@ -34,6 +35,7 @@ fun aRoomMember(
|
|||||||
role: RoomMember.Role = RoomMember.Role.User,
|
role: RoomMember.Role = RoomMember.Role.User,
|
||||||
membershipChangeReason: String? = null,
|
membershipChangeReason: String? = null,
|
||||||
isServiceMember: Boolean = false,
|
isServiceMember: Boolean = false,
|
||||||
|
userStatus: DisplayedStatus? = null,
|
||||||
) = RoomMember(
|
) = RoomMember(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
@@ -45,6 +47,7 @@ fun aRoomMember(
|
|||||||
role = role,
|
role = role,
|
||||||
membershipChangeReason = membershipChangeReason,
|
membershipChangeReason = membershipChangeReason,
|
||||||
isServiceMember = isServiceMember,
|
isServiceMember = isServiceMember,
|
||||||
|
displayedStatus = userStatus,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aRoomMemberList() = persistentListOf(
|
fun aRoomMemberList() = persistentListOf(
|
||||||
|
|||||||
+21
-9
@@ -39,11 +39,17 @@ class MentionSpan(
|
|||||||
private var typeface: Typeface = Typeface.DEFAULT
|
private var typeface: Typeface = Typeface.DEFAULT
|
||||||
|
|
||||||
private var measuredTextWidth = 0
|
private var measuredTextWidth = 0
|
||||||
|
private var measuredTrailingWidth = 0
|
||||||
|
|
||||||
// The formatted display text, will be set by the formatter
|
// The formatted display text, will be set by the formatter
|
||||||
var displayText: CharSequence = ""
|
var displayText: CharSequence = ""
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var trailingText: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value?.let { " $it" }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the visual properties of this span.
|
* Updates the visual properties of this span.
|
||||||
*/
|
*/
|
||||||
@@ -80,6 +86,10 @@ class MentionSpan(
|
|||||||
displayText = formatter.formatDisplayText(type)
|
displayText = formatter.formatDisplayText(type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateTrailingText(trailingText: String?){
|
||||||
|
this.trailingText = trailingText
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSize(
|
override fun getSize(
|
||||||
paint: Paint,
|
paint: Paint,
|
||||||
text: CharSequence?,
|
text: CharSequence?,
|
||||||
@@ -91,7 +101,8 @@ class MentionSpan(
|
|||||||
textPaint.typeface = typeface
|
textPaint.typeface = typeface
|
||||||
// Measure the full text width without truncation
|
// Measure the full text width without truncation
|
||||||
measuredTextWidth = textPaint.measureText(displayText, 0, displayText.length).roundToInt()
|
measuredTextWidth = textPaint.measureText(displayText, 0, displayText.length).roundToInt()
|
||||||
return measuredTextWidth + startPadding + endPadding
|
measuredTrailingWidth = trailingText?.let { textPaint.measureText(it).roundToInt() } ?: 0
|
||||||
|
return measuredTextWidth + measuredTrailingWidth + startPadding + endPadding
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun draw(
|
override fun draw(
|
||||||
@@ -106,7 +117,8 @@ class MentionSpan(
|
|||||||
paint: Paint
|
paint: Paint
|
||||||
) {
|
) {
|
||||||
val availableWidth = (canvas.width - x).coerceAtLeast(0f)
|
val availableWidth = (canvas.width - x).coerceAtLeast(0f)
|
||||||
val measuredWidth = measuredTextWidth + startPadding + endPadding
|
|
||||||
|
val measuredWidth = measuredTextWidth + measuredTrailingWidth + startPadding + endPadding
|
||||||
val pillWidth = minOf(availableWidth, measuredWidth.toFloat())
|
val pillWidth = minOf(availableWidth, measuredWidth.toFloat())
|
||||||
|
|
||||||
backgroundPaint.color = backgroundColor
|
backgroundPaint.color = backgroundColor
|
||||||
@@ -118,18 +130,18 @@ class MentionSpan(
|
|||||||
textPaint.color = textColor
|
textPaint.color = textColor
|
||||||
textPaint.typeface = typeface
|
textPaint.typeface = typeface
|
||||||
|
|
||||||
val availableWidthForText = availableWidth - startPadding - endPadding
|
val availableWidthForText = availableWidth - startPadding - endPadding - measuredTrailingWidth
|
||||||
val textToDraw = if (measuredTextWidth > availableWidthForText) {
|
val textToDraw = if (measuredTextWidth > availableWidthForText) {
|
||||||
TextUtils.ellipsize(
|
TextUtils.ellipsize(displayText, textPaint, availableWidthForText, TextUtils.TruncateAt.END)
|
||||||
displayText,
|
|
||||||
textPaint,
|
|
||||||
availableWidthForText,
|
|
||||||
TextUtils.TruncateAt.END
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
displayText
|
displayText
|
||||||
}
|
}
|
||||||
canvas.drawText(textToDraw, 0, textToDraw.length, x + startPadding, y.toFloat(), textPaint)
|
canvas.drawText(textToDraw, 0, textToDraw.length, x + startPadding, y.toFloat(), textPaint)
|
||||||
|
|
||||||
|
trailingText?.let {
|
||||||
|
val drawnTextWidth = minOf(availableWidthForText, measuredTextWidth.toFloat())
|
||||||
|
canvas.drawText(it, x + startPadding + drawnTextWidth, y.toFloat(), textPaint)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
@@ -18,6 +18,7 @@ import io.element.android.compound.theme.ElementTheme
|
|||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache
|
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache
|
||||||
import io.element.android.libraries.matrix.ui.messages.RoomNamesCache
|
import io.element.android.libraries.matrix.ui.messages.RoomNamesCache
|
||||||
|
import io.element.android.libraries.matrix.ui.model.toEmojiText
|
||||||
|
|
||||||
interface MentionSpanUpdater {
|
interface MentionSpanUpdater {
|
||||||
fun updateMentionSpans(text: CharSequence): CharSequence
|
fun updateMentionSpans(text: CharSequence): CharSequence
|
||||||
@@ -48,6 +49,13 @@ class DefaultMentionSpanUpdater(
|
|||||||
for (mentionSpan in text.getMentionSpans()) {
|
for (mentionSpan in text.getMentionSpans()) {
|
||||||
mentionSpan.updateTheme(theme)
|
mentionSpan.updateTheme(theme)
|
||||||
mentionSpan.updateDisplayText(formatter)
|
mentionSpan.updateDisplayText(formatter)
|
||||||
|
mentionSpan.updateTrailingText(
|
||||||
|
if (mentionSpan.type is MentionType.User) {
|
||||||
|
roomMemberProfilesCache.getDisplayedStatus(mentionSpan.type.userId)?.toEmojiText()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user