mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 16:10:00 +00:00
Merge pull request #7232 from element-hq/feature/fga/display_user_status
WIP : display user status
This commit is contained in:
+14
-11
@@ -63,6 +63,8 @@ import io.element.android.libraries.designsystem.theme.roomListRoomName
|
||||
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
||||
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.user.DisplayedStatus
|
||||
import io.element.android.libraries.matrix.ui.components.DisplayNameWithStatus
|
||||
import io.element.android.libraries.matrix.ui.components.InviteSenderView
|
||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@@ -126,7 +128,8 @@ internal fun RoomSummaryRow(
|
||||
NameAndTimestampRow(
|
||||
name = room.name,
|
||||
timestamp = room.timestamp,
|
||||
isHighlighted = room.isHighlighted
|
||||
isHighlighted = room.isHighlighted,
|
||||
dmUserStatus = room.dmUserStatus,
|
||||
)
|
||||
MessagePreviewAndIndicatorRow(room = room, showUnreadCount = showUnreadCount)
|
||||
}
|
||||
@@ -142,7 +145,8 @@ internal fun RoomSummaryRow(
|
||||
NameAndTimestampRow(
|
||||
name = room.name,
|
||||
timestamp = null,
|
||||
isHighlighted = room.isHighlighted
|
||||
isHighlighted = room.isHighlighted,
|
||||
dmUserStatus = null,
|
||||
)
|
||||
if (room.canonicalAlias != null) {
|
||||
Text(
|
||||
@@ -218,22 +222,21 @@ private fun NameAndTimestampRow(
|
||||
name: String?,
|
||||
timestamp: String?,
|
||||
isHighlighted: Boolean,
|
||||
dmUserStatus: DisplayedStatus?,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalArrangement = spacedBy(16.dp)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.clipToBounds(),
|
||||
val displayName = name?.toSafeLength(ellipsize = true) ?: stringResource(id = CommonStrings.common_no_room_name)
|
||||
DisplayNameWithStatus(
|
||||
name = displayName,
|
||||
status = dmUserStatus,
|
||||
modifier = Modifier.weight(1f),
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
text = name?.toSafeLength(ellipsize = true) ?: stringResource(id = CommonStrings.common_no_room_name),
|
||||
fontStyle = FontStyle.Italic.takeIf { name == null },
|
||||
color = ElementTheme.colors.roomListRoomName,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
nameColor = ElementTheme.colors.roomListRoomName,
|
||||
nameFontStyle = FontStyle.Italic.takeIf { name == null },
|
||||
)
|
||||
// Timestamp
|
||||
Text(
|
||||
|
||||
+2
@@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.room.CallIntentConsensus
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.roomlist.LatestEventValue
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||
import io.element.android.libraries.matrix.ui.model.dmUserStatus
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -76,6 +77,7 @@ class RoomListRoomSummaryFactory(
|
||||
}.toImmutableList(),
|
||||
isTombstoned = roomInfo.successorRoom != null,
|
||||
isSpace = roomInfo.isSpace,
|
||||
dmUserStatus = roomInfo.dmUserStatus(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+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.notification.CallIntent
|
||||
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 kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@@ -42,6 +43,7 @@ data class RoomListRoomSummary(
|
||||
val isTombstoned: Boolean,
|
||||
val heroes: ImmutableList<AvatarData>,
|
||||
val isSpace: Boolean,
|
||||
val dmUserStatus: DisplayedStatus?,
|
||||
) {
|
||||
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
|
||||
(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.notification.CallIntent
|
||||
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 kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@@ -182,6 +183,7 @@ internal fun aRoomListRoomSummary(
|
||||
heroes: List<AvatarData> = emptyList(),
|
||||
isTombstoned: Boolean = false,
|
||||
isSpace: Boolean = false,
|
||||
dmUserStatus: DisplayedStatus? = null,
|
||||
) = RoomListRoomSummary(
|
||||
id = id,
|
||||
roomId = RoomId(id),
|
||||
@@ -204,5 +206,6 @@ internal fun aRoomListRoomSummary(
|
||||
canonicalAlias = canonicalAlias,
|
||||
heroes = heroes.toImmutableList(),
|
||||
isTombstoned = isTombstoned,
|
||||
isSpace = isSpace
|
||||
isSpace = isSpace,
|
||||
dmUserStatus = dmUserStatus,
|
||||
)
|
||||
|
||||
+4
-1
@@ -12,6 +12,7 @@ import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -87,6 +88,7 @@ internal fun createRoomListRoomSummary(
|
||||
timestamp: String? = null,
|
||||
isTombstoned: Boolean = false,
|
||||
isSpace: Boolean = false,
|
||||
dmUserStatus: DisplayedStatus? = null,
|
||||
) = RoomListRoomSummary(
|
||||
id = A_ROOM_ID.value,
|
||||
roomId = A_ROOM_ID,
|
||||
@@ -109,5 +111,6 @@ internal fun createRoomListRoomSummary(
|
||||
isDm = false,
|
||||
heroes = heroes.toImmutableList(),
|
||||
isTombstoned = isTombstoned,
|
||||
isSpace = isSpace
|
||||
isSpace = isSpace,
|
||||
dmUserStatus = dmUserStatus,
|
||||
)
|
||||
|
||||
+2
@@ -84,6 +84,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsSta
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.map
|
||||
import io.element.android.libraries.matrix.ui.model.dmUserStatus
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
import io.element.android.libraries.matrix.ui.room.getDirectRoomMember
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
@@ -319,6 +320,7 @@ class MessagesPresenter(
|
||||
appName = buildMeta.applicationName,
|
||||
pinnedMessagesBannerState = pinnedMessagesBannerState,
|
||||
dmUserVerificationState = dmUserVerificationState,
|
||||
dmUserStatus = roomInfo.dmUserStatus(),
|
||||
roomMemberModerationState = roomMemberModerationState,
|
||||
topBarSharedHistoryIcon = topBarSharedHistoryIcon,
|
||||
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.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,
|
||||
|
||||
+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.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
|
||||
@@ -128,6 +129,7 @@ fun aMessagesState(
|
||||
hasUnreadThreads = false,
|
||||
),
|
||||
isCurrentlySharingLiveLocationInRoom: Boolean = false,
|
||||
dmUserStatus: DisplayedStatus? = null,
|
||||
eventSink: (MessagesEvent) -> Unit = {},
|
||||
) = MessagesState(
|
||||
roomId = RoomId("!id:domain"),
|
||||
@@ -158,6 +160,7 @@ fun aMessagesState(
|
||||
successorRoom = successorRoom,
|
||||
threads = threads,
|
||||
showLiveLocationShareBanner = isCurrentlySharingLiveLocationInRoom,
|
||||
dmUserStatus = dmUserStatus,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
|
||||
+1
@@ -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 = {
|
||||
|
||||
+13
-6
@@ -41,6 +41,8 @@ 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.api.room.getBestName
|
||||
import io.element.android.libraries.matrix.ui.components.DisplayNameWithStatus
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
import io.element.android.libraries.slashcommands.api.SlashCommandSuggestion
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion
|
||||
@@ -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.getBestName()
|
||||
is ResolvedSuggestion.Alias -> suggestion.roomName
|
||||
is ResolvedSuggestion.Command -> suggestion.command.command
|
||||
}
|
||||
val status = when (suggestion) {
|
||||
is ResolvedSuggestion.Member -> suggestion.roomMember.displayedStatus
|
||||
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 = status,
|
||||
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(
|
||||
|
||||
+6
-1
@@ -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!",
|
||||
|
||||
+1
@@ -166,6 +166,7 @@ internal fun MessageShieldViewPreview() {
|
||||
displayName = USER_NAME_ALICE,
|
||||
displayNameAmbiguous = false,
|
||||
avatarUrl = null,
|
||||
displayedStatus = null,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
+12
-2
@@ -315,7 +315,12 @@ private fun inReplyToInvalidContent(): InReplyToDetails.Ready = InReplyToDetails
|
||||
)
|
||||
),
|
||||
textContent = "A text content",
|
||||
senderProfile = ProfileDetails.Ready(displayName = "Sender", displayNameAmbiguous = false, avatarUrl = null)
|
||||
senderProfile = ProfileDetails.Ready(
|
||||
displayName = "Sender",
|
||||
displayNameAmbiguous = false,
|
||||
avatarUrl = null,
|
||||
displayedStatus = null
|
||||
)
|
||||
)
|
||||
|
||||
private fun inReplyToTextContent(): InReplyToDetails.Ready = InReplyToDetails.Ready(
|
||||
@@ -332,5 +337,10 @@ private fun inReplyToTextContent(): InReplyToDetails.Ready = InReplyToDetails.Re
|
||||
)
|
||||
),
|
||||
textContent = "A text content",
|
||||
senderProfile = ProfileDetails.Ready(displayName = "Sender", displayNameAmbiguous = false, avatarUrl = null)
|
||||
senderProfile = ProfileDetails.Ready(
|
||||
displayName = "Sender",
|
||||
displayNameAmbiguous = false,
|
||||
avatarUrl = null,
|
||||
displayedStatus = null
|
||||
)
|
||||
)
|
||||
|
||||
+2
@@ -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,
|
||||
)
|
||||
|
||||
+19
-9
@@ -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,9 +45,10 @@ 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.ui.strings.CommonStrings
|
||||
@@ -65,6 +65,7 @@ internal fun MessagesViewTopBar(
|
||||
heroes: ImmutableList<AvatarData>,
|
||||
dmUserIdentityState: IdentityState?,
|
||||
sharedHistoryIcon: SharedHistoryIcon,
|
||||
dmUserStatus: DisplayedStatus?,
|
||||
onRoomDetailsClick: () -> Unit,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -91,6 +92,7 @@ internal fun MessagesViewTopBar(
|
||||
roomAvatar = roomAvatar,
|
||||
isTombstoned = isTombstoned,
|
||||
heroes = heroes,
|
||||
dmUserStatus = dmUserStatus,
|
||||
modifier = titleModifier
|
||||
)
|
||||
|
||||
@@ -144,6 +146,7 @@ private fun RoomAvatarAndNameRow(
|
||||
roomAvatar: AvatarData,
|
||||
heroes: ImmutableList<AvatarData>,
|
||||
isTombstoned: Boolean,
|
||||
dmUserStatus: DisplayedStatus?,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
@@ -157,14 +160,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,
|
||||
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 +186,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 +195,7 @@ internal fun MessagesViewTopBarPreview() = ElementPreview {
|
||||
heroes = heroes,
|
||||
dmUserIdentityState = dmUserIdentityState,
|
||||
sharedHistoryIcon = sharedHistoryIcon,
|
||||
dmUserStatus = dmUserStatus,
|
||||
onRoomDetailsClick = {},
|
||||
onBackClick = {},
|
||||
menuActions = {
|
||||
@@ -246,5 +250,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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -41,9 +41,9 @@ sealed interface EmojiPickerSheetState {
|
||||
}
|
||||
|
||||
enum class PredefinedUserStatus(val emoji: String, @StringRes val labelRes: Int) {
|
||||
IN_A_MEETING("💬", R.string.common_user_status_in_a_meeting),
|
||||
FOCUS_TIME("💡", R.string.common_user_status_focus_time),
|
||||
ON_THE_ROAD("🚙", R.string.common_user_status_on_the_road),
|
||||
BE_RIGHT_BACK("☕", R.string.common_user_status_be_right_back),
|
||||
AWAY("🌴", R.string.common_user_status_away),
|
||||
IN_A_MEETING("💬", R.string.screen_settings_user_status_in_a_meeting),
|
||||
FOCUS_TIME("💡", R.string.screen_settings_user_status_focus_time),
|
||||
ON_THE_ROAD("🚙", R.string.screen_settings_user_status_on_the_road),
|
||||
BE_RIGHT_BACK("☕", R.string.screen_settings_user_status_be_right_back),
|
||||
AWAY("🌴", R.string.screen_settings_user_status_away),
|
||||
}
|
||||
|
||||
+4
-4
@@ -132,7 +132,7 @@ private fun EmptyStatusRow(
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = stringResource(R.string.screen_preferences_user_status_placeholder),
|
||||
text = stringResource(R.string.screen_settings_user_status_placeholder),
|
||||
modifier = Modifier.padding(vertical = 16.dp),
|
||||
)
|
||||
},
|
||||
@@ -150,7 +150,7 @@ private fun CurrentStatusRow(
|
||||
) {
|
||||
val (emoji, text) = when (displayedStatus) {
|
||||
is DisplayedStatus.UserSet -> displayedStatus.status.emoji to displayedStatus.status.text
|
||||
is DisplayedStatus.InCall -> "🎧" to stringResource(R.string.common_user_status_on_a_call)
|
||||
is DisplayedStatus.InCall -> "🎧" to stringResource(CommonStrings.common_on_a_call)
|
||||
}
|
||||
ListItem(
|
||||
headlineContent = { Text(text = text, modifier = Modifier.padding(vertical = 16.dp)) },
|
||||
@@ -225,7 +225,7 @@ private fun UserStatusPickerBottomSheet(
|
||||
}
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(text = stringResource(R.string.common_user_status_custom))
|
||||
Text(text = stringResource(R.string.screen_settings_user_status_custom))
|
||||
},
|
||||
leadingContent = ListItemContent.Custom { EmojiText("✏️") },
|
||||
onClick = {
|
||||
@@ -265,7 +265,7 @@ private fun CustomStatusInputRow(
|
||||
TextField(
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
state = textFieldState,
|
||||
placeholder = stringResource(R.string.screen_preferences_user_status_custom_hint),
|
||||
placeholder = stringResource(R.string.screen_settings_user_status_custom_hint),
|
||||
inputTransformation = InputTransformation.maxLength(CUSTOM_STATUS_TEXT_MAX_LENGTH),
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
||||
onKeyboardAction = { if (hasChanges) onConfirm() },
|
||||
|
||||
@@ -101,6 +101,14 @@ If you proceed, some of your settings may change."</string>
|
||||
<string name="screen_notification_settings_system_notifications_action_required_content_link">"system settings"</string>
|
||||
<string name="screen_notification_settings_system_notifications_turned_off">"System notifications turned off"</string>
|
||||
<string name="screen_notification_settings_title">"Notifications"</string>
|
||||
<string name="screen_settings_user_status_away">"Away"</string>
|
||||
<string name="screen_settings_user_status_be_right_back">"Be right back"</string>
|
||||
<string name="screen_settings_user_status_custom">"Custom…"</string>
|
||||
<string name="screen_settings_user_status_custom_hint">"Custom status…"</string>
|
||||
<string name="screen_settings_user_status_focus_time">"Focus time"</string>
|
||||
<string name="screen_settings_user_status_in_a_meeting">"In a meeting"</string>
|
||||
<string name="screen_settings_user_status_on_the_road">"On the road"</string>
|
||||
<string name="screen_settings_user_status_placeholder">"What\'s your status?"</string>
|
||||
<string name="theme_black">"Black"</string>
|
||||
<string name="theme_dark">"Dark"</string>
|
||||
<string name="theme_light">"Light"</string>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<?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_in_a_meeting">In a meeting</string>
|
||||
<string name="common_user_status_focus_time">Focus time</string>
|
||||
<string name="common_user_status_on_the_road">On the road</string>
|
||||
<string name="common_user_status_be_right_back">Be right back</string>
|
||||
<string name="common_user_status_away">Away</string>
|
||||
<string name="common_user_status_on_a_call">On a call</string>
|
||||
<string name="common_user_status_custom">Custom…</string>
|
||||
<string name="screen_preferences_user_status_placeholder">What\'s your status?</string>
|
||||
<string name="screen_preferences_user_status_custom_hint">Custom status…</string>
|
||||
</resources>
|
||||
+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.RoomNotificationSettings
|
||||
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.ui.components.aMatrixUserList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -75,6 +76,7 @@ fun aDmRoomMember(
|
||||
role: RoomMember.Role = RoomMember.Role.User,
|
||||
membershipChangeReason: String? = null,
|
||||
isServiceMember: Boolean = false,
|
||||
displayedStatus: DisplayedStatus? = null,
|
||||
) = RoomMember(
|
||||
userId = userId,
|
||||
displayName = displayName,
|
||||
@@ -86,6 +88,7 @@ fun aDmRoomMember(
|
||||
role = role,
|
||||
membershipChangeReason = membershipChangeReason,
|
||||
isServiceMember = isServiceMember,
|
||||
displayedStatus = displayedStatus,
|
||||
)
|
||||
|
||||
fun aRoomDetailsState(
|
||||
@@ -178,12 +181,13 @@ fun aDmRoomDetailsState(
|
||||
roomName: String = "Daniel",
|
||||
isEncrypted: Boolean = true,
|
||||
dmRoomMemberVerificationState: UserProfileVerificationState = UserProfileVerificationState.UNKNOWN,
|
||||
displayedStatus: DisplayedStatus? = null,
|
||||
) = aRoomDetailsState(
|
||||
roomName = roomName,
|
||||
isPublic = false,
|
||||
isEncrypted = isEncrypted,
|
||||
canInvite = true,
|
||||
roomType = RoomDetailsType.Dm(otherMember = aDmRoomMember(isIgnored = isDmMemberIgnored)),
|
||||
roomType = RoomDetailsType.Dm(otherMember = aDmRoomMember(isIgnored = isDmMemberIgnored, displayedStatus = displayedStatus)),
|
||||
dmOtherMemberDetailsState = aUserProfileState(
|
||||
isBlocked = AsyncData.Success(isDmMemberIgnored),
|
||||
verificationState = dmRoomMemberVerificationState,
|
||||
|
||||
+36
-25
@@ -37,6 +37,7 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.unit.dp
|
||||
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.user.MatrixUser
|
||||
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.testTag
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@@ -489,11 +491,23 @@ private fun RoomHeaderSection(
|
||||
}
|
||||
.testTag(TestTags.roomDetailAvatar)
|
||||
)
|
||||
TitleAndSubtitle(
|
||||
title = roomName,
|
||||
subtitle = roomAlias?.value,
|
||||
onSubtitleClick = onSubtitleClick,
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Text(
|
||||
text = roomName,
|
||||
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)
|
||||
)
|
||||
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))
|
||||
Text(
|
||||
text = title,
|
||||
text = roomName,
|
||||
style = ElementTheme.typography.fontHeadingLgBold,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
if (subtitle != null) {
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
val userStatus = otherMember.displayedStatus?.toText()
|
||||
if (userStatus != null) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Text(
|
||||
modifier = Modifier.niceClickable { onSubtitleClick(subtitle) },
|
||||
text = subtitle,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
text = userStatus,
|
||||
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,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
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.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMemberListState> {
|
||||
@@ -129,6 +130,7 @@ fun aRoomMember(
|
||||
role: RoomMember.Role = RoomMember.Role.User,
|
||||
membershipChangeReason: String? = null,
|
||||
isServiceMember: Boolean = false,
|
||||
displayedStatus: DisplayedStatus? = null,
|
||||
) = RoomMember(
|
||||
userId = userId,
|
||||
displayName = displayName,
|
||||
@@ -140,6 +142,7 @@ fun aRoomMember(
|
||||
role = role,
|
||||
membershipChangeReason = membershipChangeReason,
|
||||
isServiceMember = isServiceMember,
|
||||
displayedStatus = displayedStatus,
|
||||
)
|
||||
|
||||
fun aRoomMemberList() = persistentListOf(
|
||||
|
||||
+1
@@ -128,6 +128,7 @@ class RoomMemberDetailsPresenter(
|
||||
return userProfileState.copy(
|
||||
userName = roomUserName ?: userProfileState.userName,
|
||||
avatarUrl = roomUserAvatar ?: userProfileState.avatarUrl,
|
||||
displayedStatus = roomMember?.displayedStatus ?: userProfileState.displayedStatus,
|
||||
verificationState = verificationState,
|
||||
snackbarMessage = snackbarMessage,
|
||||
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.designsystem.preview.USER_NAME_ALICE
|
||||
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 kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@@ -82,6 +83,7 @@ fun anAlice() = MatrixUser(
|
||||
userId = UserId(value = "@alice:server.org"),
|
||||
displayName = USER_NAME_ALICE,
|
||||
avatarUrl = null,
|
||||
displayedStatus = DisplayedStatus.InCall(0L),
|
||||
)
|
||||
|
||||
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.ui.model.getAvatarData
|
||||
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 kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -264,9 +265,23 @@ private fun RoomMemberActionsBottomSheet(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp)
|
||||
.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
|
||||
if (bestName != user.userId.value) {
|
||||
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.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
|
||||
data class UserProfileState(
|
||||
val userId: UserId,
|
||||
@@ -26,6 +27,7 @@ data class UserProfileState(
|
||||
val dmRoomId: RoomId?,
|
||||
val canCall: Boolean,
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
val displayedStatus: DisplayedStatus?,
|
||||
val eventSink: (UserProfileEvents) -> Unit
|
||||
) {
|
||||
enum class ConfirmationDialog {
|
||||
|
||||
+1
@@ -145,6 +145,7 @@ class UserProfilePresenter(
|
||||
userId = userId,
|
||||
userName = userProfile?.displayName,
|
||||
avatarUrl = userProfile?.avatarUrl,
|
||||
displayedStatus = userProfile?.displayedStatus,
|
||||
isBlocked = isBlocked.value,
|
||||
verificationState = UserProfileVerificationState.UNKNOWN,
|
||||
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.Text
|
||||
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.testTag
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@@ -56,6 +58,7 @@ fun UserProfileHeaderSection(
|
||||
openAvatarPreview: (url: String) -> Unit,
|
||||
onUserIdClick: () -> Unit,
|
||||
withdrawVerificationClick: () -> Unit,
|
||||
displayedStatus: DisplayedStatus?,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
@@ -90,7 +93,16 @@ fun UserProfileHeaderSection(
|
||||
style = ElementTheme.typography.fontHeadingLgBold,
|
||||
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(
|
||||
modifier = Modifier.niceClickable { onUserIdClick() },
|
||||
@@ -99,6 +111,7 @@ fun UserProfileHeaderSection(
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
when (verificationState) {
|
||||
UserProfileVerificationState.UNKNOWN, UserProfileVerificationState.UNVERIFIED -> Unit
|
||||
UserProfileVerificationState.VERIFIED -> {
|
||||
@@ -143,6 +156,7 @@ internal fun UserProfileHeaderSectionPreview() = ElementPreview {
|
||||
userId = UserId("@alice:example.com"),
|
||||
userName = USER_NAME_ALICE,
|
||||
verificationState = UserProfileVerificationState.VERIFIED,
|
||||
displayedStatus = DisplayedStatus.InCall(0L),
|
||||
openAvatarPreview = {},
|
||||
onUserIdClick = {},
|
||||
withdrawVerificationClick = {},
|
||||
@@ -157,6 +171,7 @@ internal fun UserProfileHeaderSectionWithVerificationViolationPreview() = Elemen
|
||||
userId = UserId("@alice:example.com"),
|
||||
userName = USER_NAME_ALICE,
|
||||
verificationState = UserProfileVerificationState.VERIFICATION_VIOLATION,
|
||||
displayedStatus = DisplayedStatus.InCall(0L),
|
||||
openAvatarPreview = {},
|
||||
onUserIdClick = {},
|
||||
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.matrix.api.core.RoomId
|
||||
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
|
||||
|
||||
open class UserProfileStateProvider : PreviewParameterProvider<UserProfileState> {
|
||||
@@ -48,6 +49,7 @@ fun aUserProfileState(
|
||||
dmRoomId: RoomId? = null,
|
||||
canCall: Boolean = false,
|
||||
snackbarMessage: SnackbarMessage? = null,
|
||||
displayedStatus: DisplayedStatus? = null,
|
||||
eventSink: (UserProfileEvents) -> Unit = {},
|
||||
) = UserProfileState(
|
||||
userId = userId,
|
||||
@@ -61,5 +63,6 @@ fun aUserProfileState(
|
||||
dmRoomId = dmRoomId,
|
||||
canCall = canCall,
|
||||
snackbarMessage = snackbarMessage,
|
||||
displayedStatus = displayedStatus,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
+1
@@ -78,6 +78,7 @@ fun UserProfileView(
|
||||
userId = state.userId,
|
||||
userName = state.userName,
|
||||
verificationState = state.verificationState,
|
||||
displayedStatus = state.displayedStatus,
|
||||
openAvatarPreview = { avatarUrl ->
|
||||
openAvatarPreview(state.userName ?: state.userId.value, avatarUrl)
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user