diff --git a/changelog.d/2256.feature b/changelog.d/2256.feature new file mode 100644 index 0000000000..e7d33d6d11 --- /dev/null +++ b/changelog.d/2256.feature @@ -0,0 +1,3 @@ +Add moderation to rooms: + +- Sort member in room member list by powerlevel, display their roles. diff --git a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt index 0fa899e033..ee64f2b428 100644 --- a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt +++ b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt @@ -27,7 +27,6 @@ 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.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId -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.roomlist.RoomSummary import io.element.android.libraries.matrix.test.AN_AVATAR_URL @@ -38,6 +37,7 @@ 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.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.matrix.test.room.aRoomSummaryDetails import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.push.api.notifications.NotificationDrawerManager @@ -431,7 +431,7 @@ class InviteListPresenterTests { avatarUrl = null, isDirect = false, lastMessage = null, - inviter = RoomMember( + inviter = aRoomMember( userId = A_USER_ID, displayName = A_USER_NAME, avatarUrl = AN_AVATAR_URL, @@ -458,7 +458,7 @@ class InviteListPresenterTests { avatarUrl = null, isDirect = true, lastMessage = null, - inviter = RoomMember( + inviter = aRoomMember( userId = A_USER_ID, displayName = A_USER_NAME, avatarUrl = AN_AVATAR_URL, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsPickerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsPickerView.kt index 1732b36069..fd46405fe6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsPickerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/mentions/MentionSuggestionsPickerView.kt @@ -152,6 +152,7 @@ internal fun MentionSuggestionsPickerView_Preview() { powerLevel = 0L, normalizedPowerLevel = 0L, isIgnored = false, + role = RoomMember.Role.USER, ) MentionSuggestionsPickerView( roomId = RoomId("!room:matrix.org"), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt index 96ed7bad77..d8a48037fc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt @@ -103,5 +103,6 @@ private fun createDefaultRoomMemberForTyping(userId: UserId): RoomMember { powerLevel = 0, normalizedPowerLevel = 0, isIgnored = false, + role = RoomMember.Role.USER, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt index b610cc45f9..63f9f66e3a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt @@ -98,5 +98,6 @@ internal fun aTypingRoomMember( powerLevel = 0, normalizedPowerLevel = 0, isIgnored = false, + role = RoomMember.Role.USER, ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt index 51b642ea68..aaf0f9f774 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt @@ -26,8 +26,6 @@ import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesS import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState -import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID_3 @@ -178,7 +176,6 @@ class TypingNotificationPresenterTest { @Test fun `present - reserveSpace becomes true once we get the first typing notification with room members`() = runTest { - val aDefaultRoomMember = createDefaultRoomMember(A_USER_ID_2) val room = FakeMatrixRoom() val presenter = createPresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { @@ -216,27 +213,17 @@ class TypingNotificationPresenterTest { private fun createDefaultRoomMember( userId: UserId, - ) = RoomMember( + ) = aTypingRoomMember( userId = userId, displayName = null, - avatarUrl = null, - membership = RoomMembershipState.JOIN, isNameAmbiguous = false, - powerLevel = 0, - normalizedPowerLevel = 0, - isIgnored = false, ) private fun createKnownRoomMember( userId: UserId, - ) = RoomMember( + ) = aTypingRoomMember( userId = userId, displayName = "Alice Doe", - avatarUrl = "an_avatar_url", - membership = RoomMembershipState.JOIN, isNameAmbiguous = true, - powerLevel = 0, - normalizedPowerLevel = 0, - isIgnored = false, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt index 2493efc855..292ad43d0b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt @@ -55,6 +55,7 @@ fun aDmRoomMember( powerLevel: Long = 0, normalizedPowerLevel: Long = powerLevel, isIgnored: Boolean = false, + role: RoomMember.Role = RoomMember.Role.USER, ) = RoomMember( userId = userId, displayName = displayName, @@ -64,6 +65,7 @@ fun aDmRoomMember( powerLevel = powerLevel, normalizedPowerLevel = normalizedPowerLevel, isIgnored = isIgnored, + role = role, ) fun aRoomDetailsState() = RoomDetailsState( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/PowerLevelRoomMemberComparator.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/PowerLevelRoomMemberComparator.kt new file mode 100644 index 0000000000..51846f4bb7 --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/PowerLevelRoomMemberComparator.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl.members + +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.ui.room.sortingName +import java.text.Collator + +// Comparator used to sort room members by power level (descending) and then by name (ascending) +internal class PowerLevelRoomMemberComparator : Comparator { + // Used to simplify and compare unicode and ASCII chars (á == a) + private val collator = Collator.getInstance().apply { + decomposition = Collator.CANONICAL_DECOMPOSITION + } + override fun compare(o1: RoomMember, o2: RoomMember): Int { + return when { + o1.powerLevel > o2.powerLevel -> return -1 + o1.powerLevel < o2.powerLevel -> return 1 + else -> { + collator.compare(o1.sortingName(), o2.sortingName()) + } + } + } +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 399a2ad1e1..29800f6eac 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -66,7 +66,9 @@ class RoomMemberListPresenter @Inject constructor( roomMembers = AsyncData.Success( RoomMembers( invited = members.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(), - joined = members.getOrDefault(RoomMembershipState.JOIN, emptyList()).toImmutableList(), + joined = members.getOrDefault(RoomMembershipState.JOIN, emptyList()) + .sortedWith(PowerLevelRoomMemberComparator()) + .toImmutableList(), ) ) } @@ -84,7 +86,9 @@ class RoomMemberListPresenter @Inject constructor( SearchBarResultState.Results( RoomMembers( invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(), - joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList()).toImmutableList(), + joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList()) + .sortedWith(PowerLevelRoomMemberComparator()) + .toImmutableList(), ) ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt index 5d9549808c..9846401323 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt @@ -31,7 +31,7 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider Unit, modifier: Modifier = Modifier, ) { + val roleText = when (roomMember.role) { + RoomMember.Role.ADMIN -> stringResource(R.string.screen_room_member_list_role_administrator) + RoomMember.Role.MODERATOR -> stringResource(R.string.screen_room_member_list_role_moderator) + RoomMember.Role.USER -> null + } MatrixUserRow( modifier = modifier.clickable(onClick = onClick), matrixUser = MatrixUser( userId = roomMember.userId, displayName = roomMember.displayName, - avatarUrl = roomMember.avatarUrl + avatarUrl = roomMember.avatarUrl, ), avatarSize = AvatarSize.UserListItem, + trailingContent = roleText?.let { + @Composable { + Text( + text = it, + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } ) } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/PowerLevelRoomMemberComparatorTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/PowerLevelRoomMemberComparatorTest.kt new file mode 100644 index 0000000000..aa399465a0 --- /dev/null +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/PowerLevelRoomMemberComparatorTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.members + +import io.element.android.features.roomdetails.impl.members.PowerLevelRoomMemberComparator +import io.element.android.features.roomdetails.impl.members.aRoomMember +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.A_USER_ID_2 +import io.element.android.libraries.matrix.test.A_USER_ID_3 +import io.element.android.libraries.matrix.test.A_USER_ID_4 +import io.element.android.libraries.matrix.test.A_USER_ID_5 +import org.junit.Test + +class PowerLevelRoomMemberComparatorTest { + @Test + fun `order is Admin, then Moderator, then User`() { + val memberList = listOf( + aRoomMember(userId = UserId("@admin:example.com"), powerLevel = 100), + aRoomMember(userId = UserId("@moderator:example.com"), powerLevel = 50), + aRoomMember(userId = UserId("@user:example.com"), powerLevel = 0), + ).shuffled() + + val ordered = memberList.sortedWith(PowerLevelRoomMemberComparator()) + assert(ordered[0].userId == UserId("@admin:example.com")) + assert(ordered[1].userId == UserId("@moderator:example.com")) + assert(ordered[2].userId == UserId("@user:example.com")) + } + + @Test + fun `with the same power level, alphabetical ascending order for name is used`() { + val memberList = listOf( + aRoomMember(userId = A_USER_ID, displayName = "First - admin", powerLevel = 100), + aRoomMember(userId = A_USER_ID_2, displayName = "Second - admin", powerLevel = 100), + aRoomMember(userId = A_USER_ID_3, displayName = "Third - admin", powerLevel = 100), + aRoomMember(userId = A_USER_ID_4, displayName = "First - user", powerLevel = 0), + aRoomMember(userId = A_USER_ID_5, displayName = "Second - user", powerLevel = 0), + ).shuffled() + + val ordered = memberList.sortedWith(PowerLevelRoomMemberComparator()) + assert(ordered[0].userId == A_USER_ID) + assert(ordered[1].userId == A_USER_ID_2) + assert(ordered[2].userId == A_USER_ID_3) + assert(ordered[3].userId == A_USER_ID_4) + assert(ordered[4].userId == A_USER_ID_5) + } + + @Test + fun `when no names are provided, alphabetical order uses user id`() { + val memberList = listOf( + aRoomMember(userId = A_USER_ID, displayName = "Z - LAST!", powerLevel = 100), + aRoomMember(userId = A_USER_ID_2, powerLevel = 100), + aRoomMember(userId = A_USER_ID_3, powerLevel = 100), + ).shuffled() + + val ordered = memberList.sortedWith(PowerLevelRoomMemberComparator()) + assert(ordered[0].userId == A_USER_ID_2) + assert(ordered[1].userId == A_USER_ID_3) + assert(ordered[2].userId == A_USER_ID) + } + + @Test + fun `unicode characters are simplified and compared, order ignores case`() { + val memberList = listOf( + aRoomMember(userId = A_USER_ID, displayName = "First", powerLevel = 100), + aRoomMember(userId = A_USER_ID_2, displayName = "Șecond", powerLevel = 100), + aRoomMember(userId = A_USER_ID_3, displayName = "third", powerLevel = 100), + ).shuffled() + + val ordered = memberList.sortedWith(PowerLevelRoomMemberComparator()) + assert(ordered[0].userId == A_USER_ID) + assert(ordered[1].userId == A_USER_ID_2) + assert(ordered[2].userId == A_USER_ID_3) + } +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt index 908390484b..646755ee39 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt @@ -27,7 +27,17 @@ data class RoomMember( val powerLevel: Long, val normalizedPowerLevel: Long, val isIgnored: Boolean, + val role: Role, ) { + /** + * Role of the RoomMember, based on its [powerLevel]. + */ + enum class Role { + ADMIN, + MODERATOR, + USER + } + /** * Disambiguated display name for the RoomMember. * If the display name is null, the user ID is returned. @@ -49,6 +59,10 @@ enum class RoomMembershipState { LEAVE } +/** + * Returns the best name value to display for the RoomMember. + * If the [RoomMember.displayName] is present and not empty it'll be used, otherwise the [RoomMember.userId] will be used. + */ fun RoomMember.getBestName(): String { return displayName?.takeIf { it.isNotEmpty() } ?: userId.value } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixUser.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixUser.kt index 9880557e33..ef3a8a7b92 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixUser.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixUser.kt @@ -24,5 +24,5 @@ import kotlinx.parcelize.Parcelize data class MatrixUser( val userId: UserId, val displayName: String? = null, - val avatarUrl: String? = null + val avatarUrl: String? = null, ) : Parcelable diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt index e3d5b37cd4..9c38fca8a0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.impl.room.member 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 uniffi.matrix_sdk.RoomMemberRole import org.matrix.rustcomponents.sdk.MembershipState as RustMembershipState import org.matrix.rustcomponents.sdk.RoomMember as RustRoomMember @@ -33,9 +34,17 @@ object RoomMemberMapper { it.powerLevel(), it.normalizedPowerLevel(), it.isIgnored(), + mapRole(it.suggestedRoleForPowerLevel()) ) } + fun mapRole(role: RoomMemberRole): RoomMember.Role = + when (role) { + RoomMemberRole.ADMINISTRATOR -> RoomMember.Role.ADMIN + RoomMemberRole.MODERATOR -> RoomMember.Role.MODERATOR + RoomMemberRole.USER -> RoomMember.Role.USER + } + fun mapMembership(membershipState: RustMembershipState): RoomMembershipState = when (membershipState) { RustMembershipState.BAN -> RoomMembershipState.BAN diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcherTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcherTest.kt index 1c153435db..4770f1b9e2 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcherTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcherTest.kt @@ -34,6 +34,7 @@ import org.matrix.rustcomponents.sdk.NoPointer import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomMember import org.matrix.rustcomponents.sdk.RoomMembersIterator +import uniffi.matrix_sdk.RoomMemberRole class RoomMemberListFetcherTest { @Test @@ -268,6 +269,7 @@ class FakeRustRoomMember( private val membership: MembershipState = MembershipState.JOIN, private val isNameAmbiguous: Boolean = false, private val powerLevel: Long = 0L, + private val role: RoomMemberRole = RoomMemberRole.USER, ) : RoomMember(NoPointer) { override fun userId(): String { return userId.value @@ -300,4 +302,8 @@ class FakeRustRoomMember( override fun isIgnored(): Boolean { return false } + + override fun suggestedRoleForPowerLevel(): RoomMemberRole { + return role + } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomMemberFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomMemberFixture.kt index 8d43459aa8..76a1af8ac4 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomMemberFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomMemberFixture.kt @@ -29,6 +29,7 @@ fun aRoomMember( powerLevel: Long = 0L, normalizedPowerLevel: Long = 0L, isIgnored: Boolean = false, + role: RoomMember.Role = RoomMember.Role.USER, ) = RoomMember( userId = userId, displayName = displayName, @@ -38,4 +39,5 @@ fun aRoomMember( powerLevel = powerLevel, normalizedPowerLevel = normalizedPowerLevel, isIgnored = isIgnored, + role = role, ) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/RoomMemberExtensions.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/RoomMemberExtensions.kt new file mode 100644 index 0000000000..953127fc69 --- /dev/null +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/RoomMemberExtensions.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.ui.room + +import io.element.android.libraries.matrix.api.room.RoomMember + +/** + * Returns the name value to use when sorting room members. + * + * If the display name is not null and not empty, it is returned. + * Otherwise, the user ID is returned without the initial "@". + */ +fun RoomMember.sortingName(): String { + return displayName?.takeIf { it.isNotEmpty() } ?: userId.value.drop(1) +} diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Day-2_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Day-2_3_null_0,NEXUS_5,1.0,en].png index 804123b945..217cb02ff2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Day-2_3_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Day-2_3_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eba9e3f6b232813ccb2c42eb9ede3b594769619e4ce8f13b9d43c53688da070b -size 38958 +oid sha256:76bd5b2b4e4277cd9f87c8d39ec7490cde4d9de0f1f99cde8cd9061c19a9f34f +size 47105 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Day-2_3_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Day-2_3_null_6,NEXUS_5,1.0,en].png index 65e4968034..60a5a4dd2a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Day-2_3_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Day-2_3_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:839119e003cdbccea31c8a19ee50248890a3bc042a0f820163ce5fdd7411f193 -size 25282 +oid sha256:da396a8bdd3b9124c0b896e0dd79e7835091041b0544041421bf25b7f2edcaef +size 25960 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Night-2_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Night-2_4_null_0,NEXUS_5,1.0,en].png index 2a7f8a62ec..1a75ea22be 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Night-2_4_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Night-2_4_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:372179764b951cb9074a0e573241402ec2137045f26d9ffea1d567b93377c624 -size 38233 +oid sha256:02874e8bf08b040dc154b81e579c602e023abd99b2af8732a9e322921042ac8d +size 46597 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Night-2_4_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Night-2_4_null_6,NEXUS_5,1.0,en].png index cb1b703abc..ddf244e633 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Night-2_4_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.members_RoomMemberList_null_RoomMemberList-Night-2_4_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ac113aa7a641c20d9c725078509de96e6878adf95e7ff456d77c309f263a16f -size 24936 +oid sha256:3f764f7ca578de87f854d4bd090bc642a851f4ce85deee91c0ca77e3d4a5f582 +size 25653