From dcaac703fc99d982ef328853323d92e46c800206 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 20 Nov 2025 16:41:43 +0100 Subject: [PATCH 01/11] change(members): make sure state is not lost when navigating --- .../impl/members/RoomMemberListEvents.kt | 1 + .../impl/members/RoomMemberListNode.kt | 6 +- .../impl/members/RoomMemberListPresenter.kt | 18 +- .../impl/members/RoomMemberListState.kt | 6 + .../members/RoomMemberListStateProvider.kt | 207 ++++++++++-------- .../impl/members/RoomMemberListView.kt | 40 +--- 6 files changed, 153 insertions(+), 125 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt index 00332e391a..9a8798124e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt @@ -11,6 +11,7 @@ package io.element.android.features.roomdetails.impl.members import io.element.android.libraries.matrix.api.room.RoomMember sealed interface RoomMemberListEvents { + data class ChangeSelectedSection(val section: SelectedSection): RoomMemberListEvents data class UpdateSearchQuery(val query: String) : RoomMemberListEvents data class OnSearchActiveChanged(val active: Boolean) : RoomMemberListEvents data class RoomMemberSelected(val roomMember: RoomMember) : RoomMemberListEvents diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt index 4d915aa294..750b111fc3 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt @@ -9,6 +9,8 @@ package io.element.android.features.roomdetails.impl.members import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext @@ -21,6 +23,7 @@ import io.element.android.annotations.ContributesNode import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationRenderer +import io.element.android.libraries.architecture.appyx.launchMolecule import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.UserId @@ -41,6 +44,7 @@ class RoomMemberListNode( } private val callback: Callback = callback() + private val stateFlow = launchMolecule { presenter.present() } init { lifecycle.subscribe( @@ -64,7 +68,7 @@ class RoomMemberListNode( @Composable override fun View(modifier: Modifier) { - val state = presenter.present() + val state by stateFlow.collectAsState() RoomMemberListView( state = state, modifier = modifier, 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 e69ddb744a..65385e5f08 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 @@ -10,6 +10,7 @@ package io.element.android.features.roomdetails.impl.members import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -18,7 +19,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import dev.zacsweers.metro.Inject -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents.ShowActionsForUser import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter @@ -53,7 +54,7 @@ class RoomMemberListPresenter( private val roomMembersModerationPresenter: Presenter, private val encryptionService: EncryptionService, ) : Presenter { - private var roomMembers: AsyncData by mutableStateOf(AsyncData.Loading()) + private val powerLevelRoomMemberComparator = PowerLevelRoomMemberComparator() @Composable @@ -77,6 +78,9 @@ class RoomMemberListPresenter( .launchIn(this) } + var roomMembers: AsyncData by remember { mutableStateOf(AsyncData.Loading())} + var selectedSection by remember { mutableStateOf(SelectedSection.MEMBERS)} + // Update the room members when the screen is loaded LaunchedEffect(Unit) { room.updateMembers() @@ -160,7 +164,14 @@ class RoomMemberListPresenter( is RoomMemberListEvents.OnSearchActiveChanged -> isSearchActive = event.active is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query is RoomMemberListEvents.RoomMemberSelected -> - roomModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.roomMember.toMatrixUser())) + roomModerationState.eventSink(ShowActionsForUser(event.roomMember.toMatrixUser())) + is RoomMemberListEvents.ChangeSelectedSection -> selectedSection = event.section + } + } + + if (!roomModerationState.canBan && selectedSection == SelectedSection.BANNED) { + SideEffect { + selectedSection = SelectedSection.MEMBERS } } @@ -171,6 +182,7 @@ class RoomMemberListPresenter( isSearchActive = isSearchActive, canInvite = canInvite, moderationState = roomModerationState, + selectedSection = selectedSection, eventSink = ::handleEvent, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt index 404ecb523e..498dfd0180 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt @@ -21,10 +21,16 @@ data class RoomMemberListState( val searchResults: SearchBarResultState>, val isSearchActive: Boolean, val canInvite: Boolean, + val selectedSection: SelectedSection, val moderationState: RoomMemberModerationState, val eventSink: (RoomMemberListEvents) -> Unit, ) +enum class SelectedSection { + MEMBERS, + BANNED +} + data class RoomMembers( val invited: ImmutableList, val joined: ImmutableList, 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 2194019e70..312efef35c 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 @@ -21,107 +21,131 @@ import kotlinx.collections.immutable.persistentListOf internal class RoomMemberListStateProvider : PreviewParameterProvider { override val values: Sequence - get() = sequenceOf( - aRoomMemberListState( - roomMembers = AsyncData.Success( - RoomMembers( - invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()), - joined = persistentListOf(anAlice().withIdentity(), aBob().withIdentity(), aWalter().withIdentity()), - banned = persistentListOf(), - ) - ) - ), - aRoomMemberListState( - roomMembers = AsyncData.Success( - RoomMembers( - invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()), - joined = persistentListOf( - anAlice().withIdentity(identityState = IdentityState.Verified), - aBob().withIdentity(identityState = IdentityState.PinViolation), - aWalter().withIdentity(identityState = IdentityState.VerificationViolation) - ), - banned = persistentListOf(), - ) - ), - moderationState = aRoomMemberModerationState(canBan = true) - ), - aRoomMemberListState(roomMembers = AsyncData.Loading()), - aRoomMemberListState().copy(canInvite = true), - aRoomMemberListState().copy(isSearchActive = false), - aRoomMemberListState().copy(isSearchActive = true), - aRoomMemberListState().copy(isSearchActive = true, searchQuery = "someone"), - aRoomMemberListState().copy( - isSearchActive = true, - searchQuery = "@someone:matrix.org", - searchResults = SearchBarResultState.Results( - AsyncData.Success( - RoomMembers( - invited = persistentListOf(aVictor().withIdentity()), - joined = persistentListOf(anAlice().withIdentity()), - banned = persistentListOf(), - ) - ) - ), - ), - aRoomMemberListState().copy( - isSearchActive = true, - searchQuery = "something-with-no-results", - searchResults = SearchBarResultState.NoResultsFound() - ), - aRoomMemberListState( - roomMembers = AsyncData.Failure(Exception("Error details")), - ), - ) + get() = roomMemberListStates() + bannedRoomMemberListStates() } -internal class RoomMemberListStateBannedProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - aRoomMemberListState( - roomMembers = AsyncData.Success( - RoomMembers( - invited = persistentListOf(), - joined = persistentListOf(), - banned = persistentListOf( - aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(), - aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(), - aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(), - ), - ) - ), - moderationState = aRoomMemberModerationState(), +private fun roomMemberListStates(): Sequence = sequenceOf( + aRoomMemberListState( + roomMembers = AsyncData.Success( + RoomMembers( + invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()), + joined = persistentListOf(anAlice().withIdentity(), aBob().withIdentity(), aWalter().withIdentity()), + banned = persistentListOf(), ), - aRoomMemberListState( - roomMembers = AsyncData.Loading( - RoomMembers( - invited = persistentListOf(), - joined = persistentListOf(), - banned = persistentListOf( - aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(), - aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(), - aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(), - ), - ) + ), + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState( + roomMembers = AsyncData.Success( + RoomMembers( + invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()), + joined = persistentListOf( + anAlice().withIdentity(identityState = IdentityState.Verified), + aBob().withIdentity(identityState = IdentityState.PinViolation), + aWalter().withIdentity(identityState = IdentityState.VerificationViolation) ), - moderationState = aRoomMemberModerationState(), - ), - aRoomMemberListState( - roomMembers = AsyncData.Success( - RoomMembers( - invited = persistentListOf(), - joined = persistentListOf(), - banned = persistentListOf(), - ) - ), - moderationState = aRoomMemberModerationState(), + banned = persistentListOf(), ) - ) -} + ), + selectedSection = SelectedSection.MEMBERS, + moderationState = aRoomMemberModerationState(canBan = true) + ), + aRoomMemberListState( + roomMembers = AsyncData.Loading(), + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState().copy( + canInvite = true, + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState().copy( + isSearchActive = false, + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState().copy( + isSearchActive = true, + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState().copy( + isSearchActive = true, + searchQuery = "someone", + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState().copy( + isSearchActive = true, + searchQuery = "@someone:matrix.org", + searchResults = SearchBarResultState.Results( + AsyncData.Success( + RoomMembers( + invited = persistentListOf(aVictor().withIdentity()), + joined = persistentListOf(anAlice().withIdentity()), + banned = persistentListOf(), + ) + ) + ), + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState().copy( + isSearchActive = true, + searchQuery = "something-with-no-results", + searchResults = SearchBarResultState.NoResultsFound(), + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState( + roomMembers = AsyncData.Failure(Exception("Error details")), + selectedSection = SelectedSection.MEMBERS, + ), +) + +private fun bannedRoomMemberListStates(): Sequence = sequenceOf( + aRoomMemberListState( + roomMembers = AsyncData.Success( + RoomMembers( + invited = persistentListOf(), + joined = persistentListOf(), + banned = persistentListOf( + aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(), + aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(), + aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(), + ), + ) + ), + moderationState = aRoomMemberModerationState(), + selectedSection = SelectedSection.BANNED, + ), + aRoomMemberListState( + roomMembers = AsyncData.Loading( + RoomMembers( + invited = persistentListOf(), + joined = persistentListOf(), + banned = persistentListOf( + aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(), + aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(), + aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(), + ), + ) + ), + moderationState = aRoomMemberModerationState(), + selectedSection = SelectedSection.BANNED, + ), + aRoomMemberListState( + roomMembers = AsyncData.Success( + RoomMembers( + invited = persistentListOf(), + joined = persistentListOf(), + banned = persistentListOf(), + ) + ), + moderationState = aRoomMemberModerationState(), + selectedSection = SelectedSection.BANNED, + ) +) internal fun aRoomMemberListState( roomMembers: AsyncData = AsyncData.Loading(), searchResults: SearchBarResultState> = SearchBarResultState.Initial(), moderationState: RoomMemberModerationState = aRoomMemberModerationState(), + selectedSection: SelectedSection = SelectedSection.MEMBERS, ) = RoomMemberListState( roomMembers = roomMembers, searchQuery = "", @@ -129,6 +153,7 @@ internal fun aRoomMemberListState( isSearchActive = false, canInvite = false, moderationState = moderationState, + selectedSection = SelectedSection.MEMBERS, eventSink = {} ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt index 2b1ebb175c..b2a51aa446 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt @@ -30,10 +30,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -48,6 +45,7 @@ import io.element.android.features.roomdetails.impl.R import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.form.textFieldState import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon @@ -68,17 +66,11 @@ import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -private enum class SelectedSection { - MEMBERS, - BANNED -} - @Composable fun RoomMemberListView( state: RoomMemberListState, navigator: RoomMemberListNavigator, modifier: Modifier = Modifier, - initialSelectedSectionIndex: Int = 0, ) { fun onSelectUser(roomMember: RoomMember) { state.eventSink(RoomMemberListEvents.RoomMemberSelected(roomMember)) @@ -96,12 +88,6 @@ fun RoomMemberListView( } } ) { padding -> - var selectedSection by remember { mutableStateOf(SelectedSection.entries[initialSelectedSectionIndex]) } - if (!state.moderationState.canBan && selectedSection == SelectedSection.BANNED) { - SideEffect { - selectedSection = SelectedSection.MEMBERS - } - } Column( modifier = Modifier .fillMaxWidth() @@ -117,7 +103,7 @@ fun RoomMemberListView( onActiveChange = { state.eventSink(RoomMemberListEvents.OnSearchActiveChanged(it)) }, onTextChange = { state.eventSink(RoomMemberListEvents.UpdateSearchQuery(it)) }, onSelectUser = ::onSelectUser, - selectedSection = selectedSection, + selectedSection = state.selectedSection, modifier = Modifier.fillMaxWidth(), ) @@ -126,8 +112,8 @@ fun RoomMemberListView( roomMembers = state.roomMembers, showMembersCount = true, canDisplayBannedUsersControls = state.moderationState.canBan, - selectedSection = selectedSection, - onSelectedSectionChange = { selectedSection = it }, + selectedSection = state.selectedSection, + onSelectedSectionChange = { state.eventSink(RoomMemberListEvents.ChangeSelectedSection(it)) }, onSelectUser = ::onSelectUser, ) } @@ -380,9 +366,13 @@ private fun RoomMemberSearchBar( selectedSection: SelectedSection, modifier: Modifier = Modifier, ) { + var queryFieldState by textFieldState(query) SearchBar( - query = query, - onQueryChange = onTextChange, + query = queryFieldState, + onQueryChange = { newQuery -> + queryFieldState = newQuery + onTextChange(newQuery) + }, active = active, onActiveChange = onActiveChange, modifier = modifier, @@ -409,13 +399,3 @@ internal fun RoomMemberListViewPreview(@PreviewParameter(RoomMemberListStateProv navigator = object : RoomMemberListNavigator {}, ) } - -@PreviewsDayNight -@Composable -internal fun RoomMemberListViewBannedPreview(@PreviewParameter(RoomMemberListStateBannedProvider::class) state: RoomMemberListState) = ElementPreview { - RoomMemberListView( - initialSelectedSectionIndex = 1, - state = state, - navigator = object : RoomMemberListNavigator {}, - ) -} From f3f40de14a442676648d7b65416e528be2e1e2a2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 20 Nov 2025 18:27:23 +0100 Subject: [PATCH 02/11] change(members): search is now dependent of the selected section --- .../impl/members/RoomMemberListDataSource.kt | 18 +++++++++++++----- .../impl/members/RoomMemberListPresenter.kt | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt index 87ae53c93c..5e00ce0937 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt @@ -13,23 +13,31 @@ import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.room.BaseRoom 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.roomMembers import kotlinx.coroutines.withContext +import kotlin.collections.filter @Inject class RoomMemberListDataSource( private val room: BaseRoom, private val coroutineDispatchers: CoroutineDispatchers, ) { - suspend fun search(query: String): List = withContext(coroutineDispatchers.io) { + suspend fun search(query: String, selectedSection: SelectedSection): List = withContext(coroutineDispatchers.io) { val roomMembersState = room.membersStateFlow.value - val activeRoomMembers = roomMembersState.roomMembers() - ?.filter { it.membership.isActive() } + val displayableMembers = roomMembersState.roomMembers() .orEmpty() + .filter { + when(selectedSection){ + SelectedSection.MEMBERS -> it.membership.isActive() + SelectedSection.BANNED -> it.membership == RoomMembershipState.BAN + } + } + val filteredMembers = if (query.isBlank()) { - activeRoomMembers + displayableMembers } else { - activeRoomMembers.filter { member -> + displayableMembers.filter { member -> member.userId.value.contains(query, ignoreCase = true) || member.displayName?.contains(query, ignoreCase = true).orFalse() } 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 65385e5f08..400432d331 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 @@ -130,7 +130,7 @@ class RoomMemberListPresenter( searchResults = if (searchQuery.isEmpty() || !isSearchActive) { SearchBarResultState.Initial() } else { - val results = roomMemberListDataSource.search(searchQuery).groupBy { it.membership } + val results = roomMemberListDataSource.search(searchQuery, selectedSection).groupBy { it.membership } if (results.isEmpty()) { SearchBarResultState.NoResultsFound() } else { From 38eea9d4afc81af61e55f0cac3adf724e579c852 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 20 Nov 2025 20:21:02 +0100 Subject: [PATCH 03/11] change(members): update RoomMemberModerationView --- .../impl/RoomMemberModerationView.kt | 42 ++++++++++--------- .../components/avatar/AvatarSize.kt | 2 +- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt index 1e7251b20b..ac6c5bc4cb 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt @@ -232,32 +232,34 @@ private fun RoomMemberActionsBottomSheet( avatarData = user.getAvatarData(size = AvatarSize.RoomListManageUser), avatarType = AvatarType.User, modifier = Modifier - .padding(bottom = 28.dp) - .align(Alignment.CenterHorizontally) + .padding(bottom = 24.dp) + .align(Alignment.CenterHorizontally) ) - user.displayName?.let { - Text( - text = it, - style = ElementTheme.typography.fontHeadingLgBold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center, - modifier = Modifier - .padding(start = 16.dp, end = 16.dp, bottom = 8.dp) - .fillMaxWidth() - ) - } + val bestName = user.getBestName() Text( - text = user.userId.value, - style = ElementTheme.typography.fontBodyLgRegular, - color = ElementTheme.colors.textSecondary, + text = bestName, + style = ElementTheme.typography.fontHeadingLgBold, maxLines = 1, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 8.dp) + .fillMaxWidth() + ) + // Show user ID only if it's different from the display name + if (bestName != user.userId.value) { + Text( + text = user.userId.value, + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textSecondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + modifier = Modifier .padding(horizontal = 16.dp) .fillMaxWidth() - ) + ) + } Spacer(modifier = Modifier.height(32.dp)) for (actionState in actions) { @@ -330,8 +332,8 @@ internal fun RoomMemberModerationViewPreview(@PreviewParameter(InternalRoomMembe ElementPreview { Box( modifier = Modifier - .fillMaxWidth() - .heightIn(min = 64.dp) + .fillMaxWidth() + .heightIn(min = 64.dp) ) { RoomMemberModerationView( state = state, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index 31294c378f..0f99349494 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -47,7 +47,7 @@ enum class AvatarSize(val dp: Dp) { InviteSender(16.dp), EditRoomDetails(70.dp), - RoomListManageUser(70.dp), + RoomListManageUser(96.dp), NotificationsOptIn(32.dp), From 534d69f695f60791149b478b6d4026a6c2a254f2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 20 Nov 2025 21:54:01 +0100 Subject: [PATCH 04/11] design: introduce SearchField component --- .../theme/components/SearchField.kt | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchField.kt diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchField.kt new file mode 100644 index 0000000000..fc84abe827 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchField.kt @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector 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.designsystem.theme.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement.spacedBy +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.ui.strings.CommonStrings + +/** + * https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=1985-3223 + */ +@Composable +fun SearchField( + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier, + placeholder: String? = null, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + val focusManager = LocalFocusManager.current + val isFocused by interactionSource.collectIsFocusedAsState() + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier, + textStyle = textFieldStyle(), + singleLine = true, + interactionSource = interactionSource, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Search, + ), + keyboardActions = KeyboardActions( + onSearch = { + focusManager.clearFocus() + } + ), + cursorBrush = SolidColor(ElementTheme.colors.textActionAccent), + ) { innerTextField -> + DecorationBox( + isFocused = isFocused, + placeholder = placeholder, + isTextEmpty = value.isEmpty(), + innerTextField = innerTextField, + onClear = { onValueChange("") }, + ) + } +} + +@Composable +fun SearchField( + value: TextFieldValue, + onValueChange: (TextFieldValue) -> Unit, + modifier: Modifier = Modifier, + placeholder: String? = null, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + val focusManager = LocalFocusManager.current + val isFocused by interactionSource.collectIsFocusedAsState() + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier, + textStyle = textFieldStyle(), + singleLine = true, + interactionSource = interactionSource, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Search, + ), + keyboardActions = KeyboardActions( + onSearch = { + focusManager.clearFocus() + } + ), + cursorBrush = SolidColor(ElementTheme.colors.textActionAccent), + ) { innerTextField -> + DecorationBox( + isFocused = isFocused, + placeholder = placeholder, + isTextEmpty = value.text.isEmpty(), + innerTextField = innerTextField, + onClear = { TextFieldValue() } + ) + } +} + +@Composable +private fun DecorationBox( + isFocused: Boolean, + placeholder: String?, + isTextEmpty: Boolean, + onClear: () -> Unit, + innerTextField: @Composable () -> Unit, +) { + SearchFieldContainer( + isFocused = isFocused, + ) { + Row(modifier = Modifier.padding(start = 16.dp), verticalAlignment = Alignment.CenterVertically) { + Box(modifier = Modifier.weight(1f)) { + if (placeholder != null && isTextEmpty) { + Text( + text = placeholder, + color = ElementTheme.colors.textSecondary, + style = ElementTheme.typography.fontBodyLgRegular, + ) + } + innerTextField() + } + Spacer(modifier = Modifier.width(16.dp)) + val showClearIcon = isFocused && !isTextEmpty + IconButton(onClick = onClear, enabled = showClearIcon) { + if (showClearIcon) { + Icon( + modifier = Modifier.background(ElementTheme.colors.iconSecondary, CircleShape), + imageVector = CompoundIcons.Close(), + contentDescription = stringResource(CommonStrings.action_clear), + tint = ElementTheme.colors.iconOnSolidPrimary, + ) + } else { + Icon( + imageVector = CompoundIcons.Search(), + contentDescription = null, + tint = ElementTheme.colors.iconTertiary, + ) + } + } + } + } +} + +@Composable +private fun SearchFieldContainer( + isFocused: Boolean, + modifier: Modifier = Modifier, + content: @Composable () -> Unit +) { + Surface( + modifier = modifier, + shape = RoundedCornerShape(99.dp), + border = BorderStroke( + width = 1.dp, + color = if (isFocused) { + ElementTheme.colors.borderInteractiveHovered + } else { + ElementTheme.colors.borderInteractiveSecondary + } + ), + color = ElementTheme.colors.bgSubtleSecondary, + content = content + ) +} + +@Composable +private fun textFieldStyle(): TextStyle { + return ElementTheme.typography.fontBodyLgRegular.copy( + color = ElementTheme.colors.textPrimary + ) +} + +@Preview(group = PreviewGroup.Search, heightDp = 1000) +@Composable +internal fun SearchFieldsLightPreview() = ElementPreviewLight { ContentToPreview() } + +@Preview(group = PreviewGroup.Search, heightDp = 1000) +@Composable +internal fun SearchFieldsDarkPreview() = ElementPreviewDark { ContentToPreview() } + +@Composable +@ExcludeFromCoverage +private fun ContentToPreview() { + Column( + modifier = Modifier.padding(8.dp), + verticalArrangement = spacedBy(8.dp) + ) { + SearchField( + onValueChange = {}, + placeholder = "Search", + value = "", + ) + SearchField( + onValueChange = {}, + placeholder = "Search", + value = "Search term", + ) + } +} From b243394b67c31632f35d1f5cc8b37775f6f408f1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 21 Nov 2025 11:53:03 +0100 Subject: [PATCH 05/11] misc: AsyncData map non nullable --- .../features/space/impl/leave/LeaveSpacePresenter.kt | 2 +- .../io/element/android/libraries/architecture/AsyncData.kt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt index 1d5e614113..a331aefccd 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt @@ -88,7 +88,7 @@ class LeaveSpacePresenter( } LaunchedEffect(selectedRoomIds, leaveSpaceRooms) { selectableSpaceRooms = leaveSpaceRooms.map { - it?.others.orEmpty().map { room -> + it.others.map { room -> SelectableSpaceRoom( spaceRoom = room.spaceRoom, isLastAdmin = room.isLastAdmin, diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt index 5dcf1f933a..118addf1fe 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt @@ -163,14 +163,14 @@ suspend inline fun runUpdatingState( } inline fun AsyncData.map( - transform: (T?) -> R, + transform: (T) -> R, ): AsyncData { return when (this) { is AsyncData.Failure -> AsyncData.Failure( error = error, - prevData = transform(prevData) + prevData = prevData?.let { transform(prevData) } ) - is AsyncData.Loading -> AsyncData.Loading(transform(prevData)) + is AsyncData.Loading -> AsyncData.Loading(prevData?.let { transform(prevData) }) is AsyncData.Success -> AsyncData.Success(transform(data)) AsyncData.Uninitialized -> AsyncData.Uninitialized } From 1b0beda620e7a6e5717a806776703bc7d5509615 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 21 Nov 2025 11:54:07 +0100 Subject: [PATCH 06/11] change(members): use SearchField and update strings --- .../impl/src/main/res/values/localazy.xml | 6 +- .../impl/members/RoomMemberListDataSource.kt | 47 ----- .../impl/members/RoomMemberListEvents.kt | 1 - .../impl/members/RoomMemberListPresenter.kt | 50 +---- .../impl/members/RoomMemberListState.kt | 22 ++- .../members/RoomMemberListStateProvider.kt | 20 +- .../impl/members/RoomMemberListView.kt | 184 +++++++----------- .../impl/src/main/res/values/localazy.xml | 8 +- tools/localazy/config.json | 1 + 9 files changed, 113 insertions(+), 226 deletions(-) delete mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt diff --git a/features/rolesandpermissions/impl/src/main/res/values/localazy.xml b/features/rolesandpermissions/impl/src/main/res/values/localazy.xml index 2d98f43f97..77ab04749f 100644 --- a/features/rolesandpermissions/impl/src/main/res/values/localazy.xml +++ b/features/rolesandpermissions/impl/src/main/res/values/localazy.xml @@ -35,8 +35,8 @@ "Save changes?" "There are no banned users." - "%1$d person" - "%1$d people" + "%1$d Person" + "%1$d People" "Ban user" "Only remove member" @@ -45,7 +45,7 @@ "Unban user" "Banned" "Members" - "Pending" + "%1$d Invited" "Admin" "Moderator" "Owner" diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt deleted file mode 100644 index 5e00ce0937..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2023-2025 New Vector 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.features.roomdetails.impl.members - -import dev.zacsweers.metro.Inject -import io.element.android.libraries.core.bool.orFalse -import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.matrix.api.room.BaseRoom -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.roomMembers -import kotlinx.coroutines.withContext -import kotlin.collections.filter - -@Inject -class RoomMemberListDataSource( - private val room: BaseRoom, - private val coroutineDispatchers: CoroutineDispatchers, -) { - suspend fun search(query: String, selectedSection: SelectedSection): List = withContext(coroutineDispatchers.io) { - val roomMembersState = room.membersStateFlow.value - val displayableMembers = roomMembersState.roomMembers() - .orEmpty() - .filter { - when(selectedSection){ - SelectedSection.MEMBERS -> it.membership.isActive() - SelectedSection.BANNED -> it.membership == RoomMembershipState.BAN - } - } - - val filteredMembers = if (query.isBlank()) { - displayableMembers - } else { - displayableMembers.filter { member -> - member.userId.value.contains(query, ignoreCase = true) || - member.displayName?.contains(query, ignoreCase = true).orFalse() - } - } - filteredMembers - } -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt index 9a8798124e..a4b7b22bf3 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt @@ -13,6 +13,5 @@ import io.element.android.libraries.matrix.api.room.RoomMember sealed interface RoomMemberListEvents { data class ChangeSelectedSection(val section: SelectedSection): RoomMemberListEvents data class UpdateSearchQuery(val query: String) : RoomMemberListEvents - data class OnSearchActiveChanged(val active: Boolean) : RoomMemberListEvents data class RoomMemberSelected(val roomMember: RoomMember) : RoomMemberListEvents } 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 400432d331..44e0aa168f 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 @@ -23,6 +23,7 @@ import io.element.android.features.roommembermoderation.api.RoomMemberModeration import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.map import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.UserId @@ -41,7 +42,6 @@ import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext @@ -63,8 +63,6 @@ class RoomMemberListPresenter( var searchResults by remember { mutableStateOf>>(SearchBarResultState.Initial()) } - var isSearchActive by rememberSaveable { mutableStateOf(false) } - val membersState by room.membersStateFlow.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val canInvite by room.canInviteAsState(syncUpdateFlow.value) @@ -78,8 +76,9 @@ class RoomMemberListPresenter( .launchIn(this) } - var roomMembers: AsyncData by remember { mutableStateOf(AsyncData.Loading())} - var selectedSection by remember { mutableStateOf(SelectedSection.MEMBERS)} + var roomMembers: AsyncData by remember { mutableStateOf(AsyncData.Loading()) } + var selectedSection by remember { mutableStateOf(SelectedSection.MEMBERS) } + var filteredRoomMembers: AsyncData by remember { mutableStateOf(AsyncData.Loading()) } // Update the room members when the screen is loaded LaunchedEffect(Unit) { @@ -98,7 +97,7 @@ class RoomMemberListPresenter( } withContext(coroutineDispatchers.io) { val members = membersState.roomMembers().orEmpty().groupBy { it.membership } - val info = room.roomInfoFlow.first() + val info = room.info() if (members.getOrDefault(RoomMembershipState.JOIN, emptyList()).size < info.joinedMembersCount / 2) { // Don't display initial room member list if we have less than half of the joined members: // This result will come from the timeline loading membership events and it'll be wrong. @@ -125,43 +124,16 @@ class RoomMemberListPresenter( } } - LaunchedEffect(membersState, searchQuery, isSearchActive) { - withContext(coroutineDispatchers.io) { - searchResults = if (searchQuery.isEmpty() || !isSearchActive) { - SearchBarResultState.Initial() - } else { - val results = roomMemberListDataSource.search(searchQuery, selectedSection).groupBy { it.membership } - if (results.isEmpty()) { - SearchBarResultState.NoResultsFound() - } else { - val result = RoomMembers( - invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList()) - .map { it.withIdentityState(roomMemberIdentityStates) } - .toImmutableList(), - joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList()) - .sortedWith(powerLevelRoomMemberComparator) - .map { it.withIdentityState(roomMemberIdentityStates) } - .toImmutableList(), - banned = results.getOrDefault(RoomMembershipState.BAN, emptyList()) - .sortedBy { it.userId.value } - .map { it.withIdentityState(roomMemberIdentityStates) } - .toImmutableList(), - ) - SearchBarResultState.Results( - if (membersState is RoomMembersState.Pending) { - AsyncData.Loading(result) - } else { - AsyncData.Success(result) - } - ) - } + LaunchedEffect(searchQuery, roomMembers) { + filteredRoomMembers = roomMembers.map { members -> + withContext(coroutineDispatchers.io) { + members.filter(searchQuery) } } } fun handleEvent(event: RoomMemberListEvents) { when (event) { - is RoomMemberListEvents.OnSearchActiveChanged -> isSearchActive = event.active is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query is RoomMemberListEvents.RoomMemberSelected -> roomModerationState.eventSink(ShowActionsForUser(event.roomMember.toMatrixUser())) @@ -176,10 +148,8 @@ class RoomMemberListPresenter( } return RoomMemberListState( - roomMembers = roomMembers, + roomMembers = filteredRoomMembers, searchQuery = searchQuery, - searchResults = searchResults, - isSearchActive = isSearchActive, canInvite = canInvite, moderationState = roomModerationState, selectedSection = selectedSection, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt index 498dfd0180..b59f67a915 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt @@ -10,16 +10,15 @@ package io.element.android.features.roomdetails.impl.members import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.room.RoomMember import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList data class RoomMemberListState( val roomMembers: AsyncData, val searchQuery: String, - val searchResults: SearchBarResultState>, - val isSearchActive: Boolean, val canInvite: Boolean, val selectedSection: SelectedSection, val moderationState: RoomMemberModerationState, @@ -35,7 +34,22 @@ data class RoomMembers( val invited: ImmutableList, val joined: ImmutableList, val banned: ImmutableList, -) +){ + fun filter(query: String): RoomMembers { + if (query.isBlank()) { + return this + } + val filterPredicate = { member: RoomMemberWithIdentityState -> + member.roomMember.userId.value.contains(query, ignoreCase = true) || + member.roomMember.displayName?.contains(query, ignoreCase = true).orFalse() + } + return RoomMembers( + invited = invited.filter(filterPredicate).toImmutableList(), + joined = joined.filter(filterPredicate).toImmutableList(), + banned = banned.filter(filterPredicate).toImmutableList(), + ) + } +} data class RoomMemberWithIdentityState( val roomMember: RoomMember, 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 312efef35c..dafe2546d5 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 @@ -59,36 +59,21 @@ private fun roomMemberListStates(): Sequence = sequenceOf( selectedSection = SelectedSection.MEMBERS, ), aRoomMemberListState().copy( - isSearchActive = false, selectedSection = SelectedSection.MEMBERS, ), aRoomMemberListState().copy( - isSearchActive = true, selectedSection = SelectedSection.MEMBERS, ), aRoomMemberListState().copy( - isSearchActive = true, searchQuery = "someone", selectedSection = SelectedSection.MEMBERS, ), aRoomMemberListState().copy( - isSearchActive = true, searchQuery = "@someone:matrix.org", - searchResults = SearchBarResultState.Results( - AsyncData.Success( - RoomMembers( - invited = persistentListOf(aVictor().withIdentity()), - joined = persistentListOf(anAlice().withIdentity()), - banned = persistentListOf(), - ) - ) - ), selectedSection = SelectedSection.MEMBERS, ), aRoomMemberListState().copy( - isSearchActive = true, searchQuery = "something-with-no-results", - searchResults = SearchBarResultState.NoResultsFound(), selectedSection = SelectedSection.MEMBERS, ), aRoomMemberListState( @@ -143,17 +128,14 @@ private fun bannedRoomMemberListStates(): Sequence = sequen internal fun aRoomMemberListState( roomMembers: AsyncData = AsyncData.Loading(), - searchResults: SearchBarResultState> = SearchBarResultState.Initial(), moderationState: RoomMemberModerationState = aRoomMemberModerationState(), selectedSection: SelectedSection = SelectedSection.MEMBERS, ) = RoomMemberListState( roomMembers = roomMembers, searchQuery = "", - searchResults = searchResults, - isSearchActive = false, canInvite = false, moderationState = moderationState, - selectedSection = SelectedSection.MEMBERS, + selectedSection = selectedSection, eventSink = {} ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt index b2a51aa446..74d3305ff5 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt @@ -51,8 +51,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator import io.element.android.libraries.designsystem.theme.components.Scaffold -import io.element.android.libraries.designsystem.theme.components.SearchBar -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.designsystem.theme.components.SearchField import io.element.android.libraries.designsystem.theme.components.SegmentedButton import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton @@ -79,44 +78,39 @@ fun RoomMemberListView( Scaffold( modifier = modifier, topBar = { - if (!state.isSearchActive) { - RoomMemberListTopBar( - canInvite = state.canInvite, - onBackClick = navigator::exitRoomMemberList, - onInviteClick = navigator::openInviteMembers, - ) - } + RoomMemberListTopBar( + canInvite = state.canInvite, + onBackClick = navigator::exitRoomMemberList, + onInviteClick = navigator::openInviteMembers, + ) } ) { padding -> Column( modifier = Modifier - .fillMaxWidth() - .padding(padding) - .consumeWindowInsets(padding), + .fillMaxWidth() + .padding(padding) + .consumeWindowInsets(padding), verticalArrangement = Arrangement.spacedBy(16.dp), ) { - RoomMemberSearchBar( - query = state.searchQuery, - state = state.searchResults, - active = state.isSearchActive, - placeHolderTitle = stringResource(CommonStrings.common_search_for_someone), - onActiveChange = { state.eventSink(RoomMemberListEvents.OnSearchActiveChanged(it)) }, - onTextChange = { state.eventSink(RoomMemberListEvents.UpdateSearchQuery(it)) }, - onSelectUser = ::onSelectUser, - selectedSection = state.selectedSection, - modifier = Modifier.fillMaxWidth(), + var searchQuery by textFieldState(state.searchQuery) + SearchField( + value = searchQuery, + onValueChange = { newQuery -> + searchQuery = newQuery + state.eventSink(RoomMemberListEvents.UpdateSearchQuery(newQuery)) + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + placeholder = stringResource(CommonStrings.common_search_for_someone), + ) + RoomMemberList( + roomMembers = state.roomMembers, + selectedSection = state.selectedSection, + onSelectedSectionChange = { state.eventSink(RoomMemberListEvents.ChangeSelectedSection(it)) }, + showSections = state.moderationState.canBan, + onSelectUser = ::onSelectUser, ) - - if (!state.isSearchActive) { - RoomMemberList( - roomMembers = state.roomMembers, - showMembersCount = true, - canDisplayBannedUsersControls = state.moderationState.canBan, - selectedSection = state.selectedSection, - onSelectedSectionChange = { state.eventSink(RoomMemberListEvents.ChangeSelectedSection(it)) }, - onSelectUser = ::onSelectUser, - ) - } } } } @@ -124,25 +118,24 @@ fun RoomMemberListView( @Composable private fun RoomMemberList( roomMembers: AsyncData, - showMembersCount: Boolean, selectedSection: SelectedSection, + showSections: Boolean = true, onSelectedSectionChange: (SelectedSection) -> Unit, - canDisplayBannedUsersControls: Boolean, onSelectUser: (RoomMember) -> Unit, ) { LazyColumn(modifier = Modifier.fillMaxWidth(), state = rememberLazyListState()) { stickyHeader { Column { - if (canDisplayBannedUsersControls) { + if (showSections) { val segmentedButtonTitles = persistentListOf( stringResource(id = R.string.screen_room_member_list_mode_members), stringResource(id = R.string.screen_room_member_list_mode_banned), ) SingleChoiceSegmentedButtonRow( modifier = Modifier - .background(ElementTheme.colors.bgCanvasDefault) - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), + .background(ElementTheme.colors.bgCanvasDefault) + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), ) { for ((index, title) in segmentedButtonTitles.withIndex()) { SegmentedButton( @@ -171,7 +164,6 @@ private fun RoomMemberList( roomMembers = roomMembers.dataOrNull() ?: return@LazyColumn, selectedSection = selectedSection, onSelectUser = onSelectUser, - showMembersCount = showMembersCount, ) AsyncData.Uninitialized -> Unit } @@ -182,27 +174,29 @@ private fun LazyListScope.memberItems( roomMembers: RoomMembers, selectedSection: SelectedSection, onSelectUser: (RoomMember) -> Unit, - showMembersCount: Boolean, ) { when (selectedSection) { SelectedSection.MEMBERS -> { if (roomMembers.invited.isNotEmpty()) { - roomMemberListSection( - headerText = { stringResource(id = R.string.screen_room_member_list_pending_header_title) }, + roomMemberListSectionHeader( + text = { + val memberCount = roomMembers.invited.count() + stringResource(id = R.string.screen_room_member_list_pending_header_title, memberCount) + }, + ) + roomMemberListSectionItems( members = roomMembers.invited, onMemberSelected = { onSelectUser(it) } ) } if (roomMembers.joined.isNotEmpty()) { - roomMemberListSection( - headerText = { - if (showMembersCount) { - val memberCount = roomMembers.joined.count() - pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount) - } else { - stringResource(id = R.string.screen_room_member_list_room_members_header_title) - } + roomMemberListSectionHeader( + text = { + val memberCount = roomMembers.joined.count() + pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount) }, + ) + roomMemberListSectionItems( members = roomMembers.joined, onMemberSelected = { onSelectUser(it) } ) @@ -210,8 +204,14 @@ private fun LazyListScope.memberItems( } SelectedSection.BANNED -> { // Banned users if (roomMembers.banned.isNotEmpty()) { - roomMemberListSection( - headerText = null, + roomMemberListSectionHeader( + text = { + val memberCount = roomMembers.banned.count() + stringResource(id = R.string.screen_room_member_list_banned_header_title, memberCount) + }, + isCritical = true, + ) + roomMemberListSectionItems( members = roomMembers.banned, onMemberSelected = { onSelectUser(it) } ) @@ -219,13 +219,13 @@ private fun LazyListScope.memberItems( item { Box( Modifier - .fillParentMaxSize() - .padding(horizontal = 16.dp) + .fillParentMaxSize() + .padding(horizontal = 16.dp) ) { Text( modifier = Modifier - .padding(bottom = 56.dp) - .align(Alignment.Center), + .padding(bottom = 56.dp) + .align(Alignment.Center), text = stringResource(id = R.string.screen_room_member_list_banned_empty), color = ElementTheme.colors.textSecondary, textAlign = TextAlign.Center, @@ -241,8 +241,8 @@ private fun LazyListScope.failureItem(failure: Throwable) { item { Text( modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 32.dp), + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 32.dp), text = stringResource(id = CommonStrings.error_unknown) + "\n\n" + failure.localizedMessage, color = ElementTheme.colors.textCriticalPrimary, textAlign = TextAlign.Center, @@ -250,21 +250,25 @@ private fun LazyListScope.failureItem(failure: Throwable) { } } -private fun LazyListScope.roomMemberListSection( - headerText: @Composable (() -> String)?, +private fun LazyListScope.roomMemberListSectionHeader( + text: @Composable (() -> String), + modifier: Modifier = Modifier, + isCritical: Boolean = false, +) { + item { + Text( + modifier = modifier.padding(horizontal = 16.dp, vertical = 12.dp), + text = text(), + style = ElementTheme.typography.fontBodyLgMedium, + color = if (isCritical) ElementTheme.colors.textCriticalPrimary else ElementTheme.colors.textPrimary, + ) + } +} + +private fun LazyListScope.roomMemberListSectionItems( members: ImmutableList?, onMemberSelected: (RoomMember) -> Unit, ) { - headerText?.let { - item { - Text( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), - text = it(), - style = ElementTheme.typography.fontBodyLgRegular, - color = ElementTheme.colors.textSecondary, - ) - } - } items(members.orEmpty()) { matrixUser -> RoomMemberListItem( modifier = Modifier.fillMaxWidth(), @@ -353,44 +357,6 @@ private fun RoomMemberListTopBar( ) } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun RoomMemberSearchBar( - query: String, - state: SearchBarResultState>, - active: Boolean, - placeHolderTitle: String, - onActiveChange: (Boolean) -> Unit, - onTextChange: (String) -> Unit, - onSelectUser: (RoomMember) -> Unit, - selectedSection: SelectedSection, - modifier: Modifier = Modifier, -) { - var queryFieldState by textFieldState(query) - SearchBar( - query = queryFieldState, - onQueryChange = { newQuery -> - queryFieldState = newQuery - onTextChange(newQuery) - }, - active = active, - onActiveChange = onActiveChange, - modifier = modifier, - placeHolderTitle = placeHolderTitle, - resultState = state, - resultHandler = { results -> - RoomMemberList( - roomMembers = results, - showMembersCount = false, - onSelectUser = { onSelectUser(it) }, - canDisplayBannedUsersControls = false, - selectedSection = selectedSection, - onSelectedSectionChange = {}, - ) - }, - ) -} - @PreviewsDayNight @Composable internal fun RoomMemberListViewPreview(@PreviewParameter(RoomMemberListStateProvider::class) state: RoomMemberListState) = ElementPreview { diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index 2f9f2de9b7..4d8e634e81 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -71,9 +71,10 @@ "Topic" "Updating room…" "There are no banned users." + "%1$d Banned" - "%1$d person" - "%1$d people" + "%1$d Person" + "%1$d People" "Ban user" "Only remove member" @@ -82,7 +83,8 @@ "Unban user" "Banned" "Members" - "Pending" + "%1$d Invited" + "Pending" "Admin" "Moderator" "Owner" diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 405451bb34..27ca1ecd12 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -198,6 +198,7 @@ "screen_room_details_.*", "screen\\.room_details\\..*", "screen_room_member_list_.*", + "screen\\.room_member_list\\..*", "screen_room_notification_settings_.*", "screen_notification_settings_edit_failed_updating_default_mode", "screen_polls_history_title", From 8fd34051e03f0da791466b97a42cb18ca9c193d2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 24 Nov 2025 10:42:03 +0100 Subject: [PATCH 07/11] change(members): new empty state for search and hide banned tabs when there is none. --- .../impl/members/RoomMemberListPresenter.kt | 18 ++-- .../impl/members/RoomMemberListState.kt | 17 +++- .../members/RoomMemberListStateProvider.kt | 1 + .../impl/members/RoomMemberListView.kt | 99 ++++++++++--------- .../impl/src/main/res/values/localazy.xml | 2 + 5 files changed, 79 insertions(+), 58 deletions(-) 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 44e0aa168f..3d59b3d922 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 @@ -49,7 +49,6 @@ import kotlinx.coroutines.withContext @Inject class RoomMemberListPresenter( private val room: JoinedRoom, - private val roomMemberListDataSource: RoomMemberListDataSource, private val coroutineDispatchers: CoroutineDispatchers, private val roomMembersModerationPresenter: Presenter, private val encryptionService: EncryptionService, @@ -141,20 +140,21 @@ class RoomMemberListPresenter( } } - if (!roomModerationState.canBan && selectedSection == SelectedSection.BANNED) { - SideEffect { - selectedSection = SelectedSection.MEMBERS - } - } - - return RoomMemberListState( - roomMembers = filteredRoomMembers, + val state = RoomMemberListState( + roomMembers = roomMembers, + filteredRoomMembers = filteredRoomMembers, searchQuery = searchQuery, canInvite = canInvite, moderationState = roomModerationState, selectedSection = selectedSection, eventSink = ::handleEvent, ) + if (!state.showBannedSection && selectedSection == SelectedSection.BANNED) { + SideEffect { + selectedSection = SelectedSection.MEMBERS + } + } + return state } private suspend fun RoomMember.withIdentityState(identityStates: ImmutableMap): RoomMemberWithIdentityState { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt index b59f67a915..007d276dd3 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt @@ -17,13 +17,17 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList data class RoomMemberListState( - val roomMembers: AsyncData, + // Only used to know if we can show the banned section + private val roomMembers: AsyncData, + val filteredRoomMembers: AsyncData, val searchQuery: String, val canInvite: Boolean, val selectedSection: SelectedSection, val moderationState: RoomMemberModerationState, val eventSink: (RoomMemberListEvents) -> Unit, -) +) { + val showBannedSection: Boolean = moderationState.canBan && roomMembers.dataOrNull()?.banned?.isNotEmpty() == true +} enum class SelectedSection { MEMBERS, @@ -34,7 +38,14 @@ data class RoomMembers( val invited: ImmutableList, val joined: ImmutableList, val banned: ImmutableList, -){ +) { + fun isEmpty(section: SelectedSection): Boolean { + return when (section) { + SelectedSection.MEMBERS -> invited.isEmpty() && joined.isEmpty() + SelectedSection.BANNED -> banned.isEmpty() + } + } + fun filter(query: String): RoomMembers { if (query.isBlank()) { return this 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 dafe2546d5..b03faa97cb 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 @@ -132,6 +132,7 @@ internal fun aRoomMemberListState( selectedSection: SelectedSection = SelectedSection.MEMBERS, ) = RoomMemberListState( roomMembers = roomMembers, + filteredRoomMembers = roomMembers, searchQuery = "", canInvite = false, moderationState = moderationState, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt index 74d3305ff5..41db02ee9d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt @@ -9,14 +9,15 @@ package io.element.android.features.roomdetails.impl.members import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandHorizontally import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkHorizontally import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.consumeWindowInsets @@ -43,6 +44,8 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.roomdetails.impl.R import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule +import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.form.textFieldState @@ -87,9 +90,9 @@ fun RoomMemberListView( ) { padding -> Column( modifier = Modifier - .fillMaxWidth() - .padding(padding) - .consumeWindowInsets(padding), + .fillMaxWidth() + .padding(padding) + .consumeWindowInsets(padding), verticalArrangement = Arrangement.spacedBy(16.dp), ) { var searchQuery by textFieldState(state.searchQuery) @@ -100,15 +103,16 @@ fun RoomMemberListView( state.eventSink(RoomMemberListEvents.UpdateSearchQuery(newQuery)) }, modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), + .fillMaxWidth() + .padding(horizontal = 16.dp), placeholder = stringResource(CommonStrings.common_search_for_someone), ) RoomMemberList( - roomMembers = state.roomMembers, + roomMembersData = state.filteredRoomMembers, selectedSection = state.selectedSection, + showBannedSection = state.showBannedSection, + searchQuery = state.searchQuery, onSelectedSectionChange = { state.eventSink(RoomMemberListEvents.ChangeSelectedSection(it)) }, - showSections = state.moderationState.canBan, onSelectUser = ::onSelectUser, ) } @@ -117,25 +121,26 @@ fun RoomMemberListView( @Composable private fun RoomMemberList( - roomMembers: AsyncData, + roomMembersData: AsyncData, selectedSection: SelectedSection, - showSections: Boolean = true, + showBannedSection: Boolean, + searchQuery: String, onSelectedSectionChange: (SelectedSection) -> Unit, onSelectUser: (RoomMember) -> Unit, ) { LazyColumn(modifier = Modifier.fillMaxWidth(), state = rememberLazyListState()) { stickyHeader { Column { - if (showSections) { + AnimatedVisibility(visible = showBannedSection) { val segmentedButtonTitles = persistentListOf( stringResource(id = R.string.screen_room_member_list_mode_members), stringResource(id = R.string.screen_room_member_list_mode_banned), ) SingleChoiceSegmentedButtonRow( modifier = Modifier - .background(ElementTheme.colors.bgCanvasDefault) - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), + .background(ElementTheme.colors.bgCanvasDefault) + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), ) { for ((index, title) in segmentedButtonTitles.withIndex()) { SegmentedButton( @@ -148,23 +153,26 @@ private fun RoomMemberList( } } } - AnimatedVisibility( - visible = roomMembers.isLoading(), - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { + AnimatedVisibility(visible = roomMembersData.isLoading()) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) } } } - when (roomMembers) { - is AsyncData.Failure -> failureItem(roomMembers.error) + when (roomMembersData) { + is AsyncData.Failure -> failureItem(roomMembersData.error) is AsyncData.Loading, - is AsyncData.Success -> memberItems( - roomMembers = roomMembers.dataOrNull() ?: return@LazyColumn, - selectedSection = selectedSection, - onSelectUser = onSelectUser, - ) + is AsyncData.Success -> { + val roomMembers = roomMembersData.dataOrNull() ?: return@LazyColumn + if (roomMembers.isEmpty(selectedSection)) { + emptySearchItem(searchQuery) + } else { + memberItems( + roomMembers = roomMembers, + selectedSection = selectedSection, + onSelectUser = onSelectUser, + ) + } + } AsyncData.Uninitialized -> Unit } } @@ -202,7 +210,7 @@ private fun LazyListScope.memberItems( ) } } - SelectedSection.BANNED -> { // Banned users + SelectedSection.BANNED -> { if (roomMembers.banned.isNotEmpty()) { roomMemberListSectionHeader( text = { @@ -215,23 +223,6 @@ private fun LazyListScope.memberItems( members = roomMembers.banned, onMemberSelected = { onSelectUser(it) } ) - } else { - item { - Box( - Modifier - .fillParentMaxSize() - .padding(horizontal = 16.dp) - ) { - Text( - modifier = Modifier - .padding(bottom = 56.dp) - .align(Alignment.Center), - text = stringResource(id = R.string.screen_room_member_list_banned_empty), - color = ElementTheme.colors.textSecondary, - textAlign = TextAlign.Center, - ) - } - } } } } @@ -241,8 +232,8 @@ private fun LazyListScope.failureItem(failure: Throwable) { item { Text( modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 32.dp), + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 32.dp), text = stringResource(id = CommonStrings.error_unknown) + "\n\n" + failure.localizedMessage, color = ElementTheme.colors.textCriticalPrimary, textAlign = TextAlign.Center, @@ -278,6 +269,22 @@ private fun LazyListScope.roomMemberListSectionItems( } } +private fun LazyListScope.emptySearchItem(searchQuery: String) { + item { + IconTitleSubtitleMolecule( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 32.dp), + iconStyle = BigIcon.Style.Default( + vectorIcon = CompoundIcons.Search(), + contentDescription = null, + ), + title = stringResource(R.string.screen_room_member_list_empty_search_title, searchQuery), + subTitle = stringResource(R.string.screen_room_member_list_empty_search_subtitle), + ) + } +} + @Composable private fun RoomMemberListItem( roomMemberWithIdentity: RoomMemberWithIdentityState, diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index 4d8e634e81..a88f910de9 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -72,6 +72,8 @@ "Updating room…" "There are no banned users." "%1$d Banned" + "Check the spelling or try a new search" + "No results for “%1$s”" "%1$d Person" "%1$d People" From bf45ed3f1f570d8d5e1c5e9dc887953377930e73 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 25 Nov 2025 12:54:35 +0100 Subject: [PATCH 08/11] change(members): update tests to match new ui and logic --- .../impl/members/RoomMemberListPresenter.kt | 6 +- .../members/RoomMemberListStateProvider.kt | 187 ++++------ .../members/RoomMemberListPresenterTest.kt | 319 +++++++----------- 3 files changed, 198 insertions(+), 314 deletions(-) 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 3d59b3d922..eb4735de6e 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 @@ -25,7 +25,6 @@ import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.map import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.identity.IdentityState @@ -59,9 +58,6 @@ class RoomMemberListPresenter( @Composable override fun present(): RoomMemberListState { var searchQuery by rememberSaveable { mutableStateOf("") } - var searchResults by remember { - mutableStateOf>>(SearchBarResultState.Initial()) - } val membersState by room.membersStateFlow.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val canInvite by room.canInviteAsState(syncUpdateFlow.value) @@ -75,8 +71,8 @@ class RoomMemberListPresenter( .launchIn(this) } - var roomMembers: AsyncData by remember { mutableStateOf(AsyncData.Loading()) } var selectedSection by remember { mutableStateOf(SelectedSection.MEMBERS) } + var roomMembers: AsyncData by remember { mutableStateOf(AsyncData.Loading()) } var filteredRoomMembers: AsyncData by remember { mutableStateOf(AsyncData.Loading()) } // Update the room members when the screen is loaded 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 b03faa97cb..580db7667a 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 @@ -12,7 +12,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.architecture.map 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 @@ -21,108 +21,59 @@ import kotlinx.collections.immutable.persistentListOf internal class RoomMemberListStateProvider : PreviewParameterProvider { override val values: Sequence - get() = roomMemberListStates() + bannedRoomMemberListStates() + get() = sequenceOf( + aRoomMemberListState( + roomMembers = AsyncData.Loading(), + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState( + roomMembers = AsyncData.Failure(Exception("Error details")), + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState( + roomMembers = aLoadedRoomMembers(), + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState( + roomMembers = aLoadedRoomMembers(), + selectedSection = SelectedSection.BANNED, + moderationState = aRoomMemberModerationState(canBan = true), + ), + aRoomMemberListState( + roomMembers = aLoadedRoomMembers(), + canInvite = true, + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState( + roomMembers = aLoadedRoomMembers(), + searchQuery = "alice", + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState( + roomMembers = aLoadedRoomMembers(), + searchQuery = "something-with-no-results", + selectedSection = SelectedSection.MEMBERS, + ), + ) } -private fun roomMemberListStates(): Sequence = sequenceOf( - aRoomMemberListState( - roomMembers = AsyncData.Success( - RoomMembers( - invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()), - joined = persistentListOf(anAlice().withIdentity(), aBob().withIdentity(), aWalter().withIdentity()), - banned = persistentListOf(), - ), +private fun aLoadedRoomMembers() = AsyncData.Success( + RoomMembers( + invited = persistentListOf( + anInvitedVictor().withIdentity(), + anInvitedWalter().withIdentity(), ), - selectedSection = SelectedSection.MEMBERS, - ), - aRoomMemberListState( - roomMembers = AsyncData.Success( - RoomMembers( - invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()), - joined = persistentListOf( - anAlice().withIdentity(identityState = IdentityState.Verified), - aBob().withIdentity(identityState = IdentityState.PinViolation), - aWalter().withIdentity(identityState = IdentityState.VerificationViolation) - ), - banned = persistentListOf(), - ) + joined = persistentListOf( + anAlice().withIdentity(identityState = IdentityState.Verified), + aBob().withIdentity(identityState = IdentityState.PinViolation), + aCarol().withIdentity(), + aDavid().withIdentity(), + anEve().withIdentity(identityState = IdentityState.VerificationViolation) ), - selectedSection = SelectedSection.MEMBERS, - moderationState = aRoomMemberModerationState(canBan = true) - ), - aRoomMemberListState( - roomMembers = AsyncData.Loading(), - selectedSection = SelectedSection.MEMBERS, - ), - aRoomMemberListState().copy( - canInvite = true, - selectedSection = SelectedSection.MEMBERS, - ), - aRoomMemberListState().copy( - selectedSection = SelectedSection.MEMBERS, - ), - aRoomMemberListState().copy( - selectedSection = SelectedSection.MEMBERS, - ), - aRoomMemberListState().copy( - searchQuery = "someone", - selectedSection = SelectedSection.MEMBERS, - ), - aRoomMemberListState().copy( - searchQuery = "@someone:matrix.org", - selectedSection = SelectedSection.MEMBERS, - ), - aRoomMemberListState().copy( - searchQuery = "something-with-no-results", - selectedSection = SelectedSection.MEMBERS, - ), - aRoomMemberListState( - roomMembers = AsyncData.Failure(Exception("Error details")), - selectedSection = SelectedSection.MEMBERS, - ), -) - -private fun bannedRoomMemberListStates(): Sequence = sequenceOf( - aRoomMemberListState( - roomMembers = AsyncData.Success( - RoomMembers( - invited = persistentListOf(), - joined = persistentListOf(), - banned = persistentListOf( - aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(), - aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(), - aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(), - ), - ) + banned = persistentListOf( + aBannedMallory().withIdentity(), + aBannedSusie().withIdentity() ), - moderationState = aRoomMemberModerationState(), - selectedSection = SelectedSection.BANNED, - ), - aRoomMemberListState( - roomMembers = AsyncData.Loading( - RoomMembers( - invited = persistentListOf(), - joined = persistentListOf(), - banned = persistentListOf( - aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(), - aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(), - aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(), - ), - ) - ), - moderationState = aRoomMemberModerationState(), - selectedSection = SelectedSection.BANNED, - ), - aRoomMemberListState( - roomMembers = AsyncData.Success( - RoomMembers( - invited = persistentListOf(), - joined = persistentListOf(), - banned = persistentListOf(), - ) - ), - moderationState = aRoomMemberModerationState(), - selectedSection = SelectedSection.BANNED, ) ) @@ -130,14 +81,17 @@ internal fun aRoomMemberListState( roomMembers: AsyncData = AsyncData.Loading(), moderationState: RoomMemberModerationState = aRoomMemberModerationState(), selectedSection: SelectedSection = SelectedSection.MEMBERS, + searchQuery: String = "", + canInvite: Boolean = false, + eventSink: (RoomMemberListEvents) -> Unit = {}, ) = RoomMemberListState( roomMembers = roomMembers, - filteredRoomMembers = roomMembers, - searchQuery = "", - canInvite = false, + filteredRoomMembers = roomMembers.map { it.filter(searchQuery) }, + searchQuery = searchQuery, + canInvite = canInvite, moderationState = moderationState, selectedSection = selectedSection, - eventSink = {} + eventSink = eventSink ) fun aRoomMemberModerationState( @@ -176,21 +130,30 @@ fun aRoomMember( fun aRoomMemberList() = persistentListOf( anAlice(), aBob(), - aRoomMember(UserId("@carol:server.org"), "Carol"), - aRoomMember(UserId("@david:server.org"), "David"), - aRoomMember(UserId("@eve:server.org"), "Eve"), - aRoomMember(UserId("@justin:server.org"), "Justin"), - aRoomMember(UserId("@mallory:server.org"), "Mallory"), - aRoomMember(UserId("@susie:server.org"), "Susie"), - aVictor(), - aWalter(), + aCarol(), + aDavid(), + anEve(), + anInvitedVictor(), + anInvitedWalter(), + aBannedSusie(), + aBannedMallory(), ) +fun anEve(): RoomMember = aRoomMember(UserId("@eve:server.org"), "Eve") + +fun aDavid(): RoomMember = aRoomMember(UserId("@david:server.org"), "David") + +fun aCarol(): RoomMember = aRoomMember(UserId("@carol:server.org"), "Carol") + fun anAlice() = aRoomMember(UserId("@alice:server.org"), "Alice", role = RoomMember.Role.Admin) fun aBob() = aRoomMember(UserId("@bob:server.org"), "Bob", role = RoomMember.Role.Moderator) -fun aVictor() = aRoomMember(UserId("@victor:server.org"), "Victor", membership = RoomMembershipState.INVITE) +fun anInvitedVictor() = aRoomMember(UserId("@victor:server.org"), "Victor", membership = RoomMembershipState.INVITE) -fun aWalter() = aRoomMember(UserId("@walter:server.org"), "Walter", membership = RoomMembershipState.INVITE) +fun anInvitedWalter() = aRoomMember(UserId("@walter:server.org"), "Walter", membership = RoomMembershipState.INVITE) + +fun aBannedSusie(): RoomMember = aRoomMember(UserId("@susie:server.org"), "Susie", membership = RoomMembershipState.BAN) + +fun aBannedMallory(): RoomMember = aRoomMember(UserId("@mallory:server.org"), "Mallory", membership = RoomMembershipState.BAN) private fun RoomMember.withIdentity(identityState: IdentityState? = null) = RoomMemberWithIdentityState(this, identityState) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt index 3c0fe34366..1548feae88 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt @@ -8,36 +8,27 @@ package io.element.android.features.roomdetails.impl.members -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState -import io.element.android.libraries.matrix.api.room.BaseRoom +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomMembersState +import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.tests.testutils.WarmUpRule -import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.test import io.element.android.tests.testutils.testCoroutineDispatchers -import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.time.withTimeout -import kotlinx.coroutines.withTimeout import org.junit.Rule import org.junit.Test -import kotlin.time.Duration.Companion.seconds @ExperimentalCoroutinesApi class RoomMemberListPresenterTest { @@ -45,176 +36,132 @@ class RoomMemberListPresenterTest { val warmUpRule = WarmUpRule() @Test - fun `member loading is done automatically on start, but is async`() = runTest { - val room = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) }, - canInviteResult = { Result.success(true) } - ).apply { - // Needed to avoid discarding the loaded members as a partial and invalid result - givenRoomInfo(aRoomInfo(joinedMembersCount = 2)) - } - ) - val presenter = createPresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + fun `initial state is loading`() = runTest { + val presenter = createPresenter() + presenter.test { skipItems(1) val initialState = awaitItem() - assertThat(initialState.roomMembers.isLoading()).isTrue() + assertThat(initialState.filteredRoomMembers.isLoading()).isTrue() assertThat(initialState.searchQuery).isEmpty() - assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) - assertThat(initialState.isSearchActive).isFalse() - room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - // Skip item while the new members state is processed - skipItems(1) - val loadedMembersState = awaitItem() - assertThat(loadedMembersState.roomMembers.isLoading()).isFalse() - assertThat(loadedMembersState.roomMembers.dataOrNull()?.invited) - .isEqualTo(listOf(RoomMemberWithIdentityState(aVictor(), null), RoomMemberWithIdentityState(aWalter(), null))) - assertThat(loadedMembersState.roomMembers.dataOrNull()?.joined).isNotEmpty() + assertThat(initialState.selectedSection).isEqualTo(SelectedSection.MEMBERS) } } @Test - fun `member loading is done automatically when RoomInfo's activeMemberCount changes`() = runTest { - val reloadMembersMutex = Mutex() - val updateMembersLambda = lambdaRecorder { - if (reloadMembersMutex.isLocked) { - reloadMembersMutex.unlock() + fun `hide banned section when there is no banned users`() = runTest { + val allRoomMembers = aRoomMemberList() + val noBannedMembers = allRoomMembers + .filterNot { it.membership == RoomMembershipState.BAN } + .toImmutableList() + val room = createFakeJoinedRoom() + .apply { + givenRoomMembersState(RoomMembersState.Ready(allRoomMembers)) } - } - val room = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = updateMembersLambda, - canInviteResult = { Result.success(true) } - ).apply { - // Needed to avoid discarding the loaded members as a partial and invalid result - givenRoomInfo(aRoomInfo(joinedMembersCount = 2)) - } - ) - val presenter = createPresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) - val initialState = awaitItem() - assertThat(initialState.roomMembers.isLoading()).isTrue() - room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - // Skip item while the new members state is processed - skipItems(1) - val loadedMembersState = awaitItem() - assertThat(loadedMembersState.roomMembers.isLoading()).isFalse() - assertThat(loadedMembersState.roomMembers.dataOrNull()?.joined).isNotEmpty() - - // Assert no events are emitted only with that change - expectNoEvents() - - // This will only progress if the `Room.updateMembers()` function is called, triggered by the RoomInfo change - withTimeout(10.seconds) { - reloadMembersMutex.withLock { - launch { room.givenRoomInfo(aRoomInfo(activeMembersCount = 0L)) } - } - } - - // Update the room members state as `Room.updateMembers()` would have done with the actual implementation - room.givenRoomMembersState(RoomMembersState.Ready(persistentListOf())) - // Wait for another update - skipItems(1) - // The members should be reloaded now - assertThat(awaitItem().roomMembers.dataOrNull()?.joined).isEmpty() - } - } - - @Test - fun `open search`() = runTest { val presenter = createPresenter( - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) }, - canInviteResult = { Result.success(true) } - ) - ) + joinedRoom = room, + roomMemberModerationState = aRoomMemberModerationState(canBan = true), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val loadedState = awaitItem() - loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true)) + assertThat(loadedState.showBannedSection).isTrue() + loadedState.eventSink(RoomMemberListEvents.ChangeSelectedSection(SelectedSection.BANNED)) + val bannedSectionState = awaitItem() + assertThat(bannedSectionState.selectedSection).isEqualTo(SelectedSection.BANNED) + // Now update the room members to have no banned users + room.givenRoomMembersState(RoomMembersState.Ready(noBannedMembers)) skipItems(1) - val searchActiveState = awaitItem() - assertThat(searchActiveState.isSearchActive).isTrue() + val noBannedMembersState = awaitItem() + assertThat(noBannedMembersState.showBannedSection).isFalse() + skipItems(1) + val finalState = awaitItem() + assertThat(finalState.selectedSection).isEqualTo(SelectedSection.MEMBERS) + } + + } + + @Test + fun `member loading is done automatically on start, but is async`() = runTest { + val room = createFakeJoinedRoom() + val presenter = createPresenter(joinedRoom = room) + presenter.test { + skipItems(1) + val initialState = awaitItem() + assertThat(initialState.filteredRoomMembers.isLoading()).isTrue() + assertThat(initialState.searchQuery).isEmpty() + room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) + // Skip items while the new members state is processed + skipItems(2) + val loadedState = awaitItem() + val loadedRoomMembers = loadedState.filteredRoomMembers.dataOrNull()!! + assertThat(loadedRoomMembers.joined).isNotEmpty() + assertThat(loadedRoomMembers.banned).isNotEmpty() + assertThat(loadedRoomMembers.invited).isNotEmpty() + assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse() + assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse() } } @Test fun `search for something which is not found`() = runTest { - val presenter = createPresenter( - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) }, - canInviteResult = { Result.success(true) } - ) - ) - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val room = createFakeJoinedRoom().apply { + givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) + } + val presenter = createPresenter(joinedRoom = room) + presenter.test { skipItems(1) val loadedState = awaitItem() - loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true)) - val searchActiveState = awaitItem() - searchActiveState.eventSink(RoomMemberListEvents.UpdateSearchQuery("something")) - skipItems(1) + val loadedRoomMembers = loadedState.filteredRoomMembers.dataOrNull()!! + assertThat(loadedRoomMembers.joined).isNotEmpty() + assertThat(loadedRoomMembers.banned).isNotEmpty() + assertThat(loadedRoomMembers.invited).isNotEmpty() + assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse() + assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse() + loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("something")) val searchQueryUpdatedState = awaitItem() assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("something") val searchSearchResultDelivered = awaitItem() - assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.NoResultsFound::class.java) + val emptyRoomMembers = searchSearchResultDelivered.filteredRoomMembers.dataOrNull()!! + assertThat(emptyRoomMembers.joined).isEmpty() + assertThat(emptyRoomMembers.banned).isEmpty() + assertThat(emptyRoomMembers.invited).isEmpty() + assertThat(emptyRoomMembers.isEmpty(SelectedSection.MEMBERS)).isTrue() + assertThat(emptyRoomMembers.isEmpty(SelectedSection.BANNED)).isTrue() } } @Test fun `search for something which is found`() = runTest { - val presenter = createPresenter( - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) }, - canInviteResult = { Result.success(true) } - ) - ) - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val room = createFakeJoinedRoom().apply { + givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) + } + val presenter = createPresenter(joinedRoom = room) + presenter.test { skipItems(1) val loadedState = awaitItem() - loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true)) - val searchActiveState = awaitItem() - searchActiveState.eventSink(RoomMemberListEvents.UpdateSearchQuery("Alice")) - skipItems(1) + val loadedRoomMembers = loadedState.filteredRoomMembers.dataOrNull()!! + assertThat(loadedRoomMembers.joined).isNotEmpty() + assertThat(loadedRoomMembers.banned).isNotEmpty() + assertThat(loadedRoomMembers.invited).isNotEmpty() + assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse() + assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse() + loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("alice")) val searchQueryUpdatedState = awaitItem() - assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("Alice") + assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("alice") val searchSearchResultDelivered = awaitItem() - assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.Results::class.java) - assertThat((searchSearchResultDelivered.searchResults as SearchBarResultState.Results).results.dataOrNull()!!.joined.first().roomMember.displayName) - .isEqualTo("Alice") + val emptyRoomMembers = searchSearchResultDelivered.filteredRoomMembers.dataOrNull()!! + assertThat(emptyRoomMembers.joined).isNotEmpty() + assertThat(emptyRoomMembers.banned).isEmpty() + assertThat(emptyRoomMembers.invited).isEmpty() + assertThat(emptyRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse() + assertThat(emptyRoomMembers.isEmpty(SelectedSection.BANNED)).isTrue() } } @Test fun `present - asynchronously sets canInvite when user has correct power level`() = runTest { - val presenter = createPresenter( - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - canInviteResult = { Result.success(true) }, - updateMembersResult = { Result.success(Unit) } - ) - ) - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val presenter = createPresenter() + presenter.test { skipItems(1) val loadedState = awaitItem() assertThat(loadedState.canInvite).isTrue() @@ -224,17 +171,11 @@ class RoomMemberListPresenterTest { @Test fun `present - asynchronously sets canInvite when user does not have correct power level`() = runTest { val presenter = createPresenter( - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - canInviteResult = { Result.success(false) }, - updateMembersResult = { Result.success(Unit) } - ) + joinedRoom = createFakeJoinedRoom( + canInviteResult = { Result.success(false) }, ) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) + presenter.test { val loadedState = awaitItem() assertThat(loadedState.canInvite).isFalse() } @@ -243,70 +184,54 @@ class RoomMemberListPresenterTest { @Test fun `present - asynchronously sets canInvite when power level check fails`() = runTest { val presenter = createPresenter( - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - canInviteResult = { Result.failure(RuntimeException("Eek")) }, - updateMembersResult = { Result.success(Unit) } - ) + joinedRoom = createFakeJoinedRoom( + canInviteResult = { Result.failure(RuntimeException("Eek")) }, ) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) + presenter.test { val loadedState = awaitItem() assertThat(loadedState.canInvite).isFalse() } } @Test - fun `present - RoomMemberSelected will open the moderation options when target user is not banned`() = runTest { - val roomMemberModerationPresenter = Presenter { - aRoomMemberModerationState(canBan = true, canKick = true) - } + fun `present - RoomMemberSelected will open the moderation options`() = runTest { val presenter = createPresenter( - roomMemberModerationPresenter = roomMemberModerationPresenter, - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) }, - canInviteResult = { Result.success(true) } - ) - ) + roomMemberModerationState = aRoomMemberModerationState(canBan = true, canKick = true) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) - awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(aVictor())) + awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(anInvitedVictor())) } } } -@ExperimentalCoroutinesApi -private fun TestScope.createDataSource( - room: BaseRoom = FakeBaseRoom().apply { - givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - }, - coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers() -) = RoomMemberListDataSource(room, coroutineDispatchers) +private fun createFakeJoinedRoom( + updateMembersResult: () -> Unit = { }, + canInviteResult: (UserId) -> Result = { Result.success(true) }, +): FakeJoinedRoom { + return FakeJoinedRoom( + baseRoom = FakeBaseRoom( + updateMembersResult = updateMembersResult, + canInviteResult = canInviteResult, + ).apply { + // Needed to avoid discarding the loaded members as a partial and invalid result + givenRoomInfo(aRoomInfo(joinedMembersCount = 2)) + } + ) +} @ExperimentalCoroutinesApi private fun TestScope.createPresenter( coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true), - joinedRoom: JoinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) } - ) - ), - roomMemberListDataSource: RoomMemberListDataSource = createDataSource(coroutineDispatchers = coroutineDispatchers), + joinedRoom: JoinedRoom = createFakeJoinedRoom(), encryptedService: FakeEncryptionService = FakeEncryptionService(), - roomMemberModerationPresenter: Presenter = Presenter { - aRoomMemberModerationState() - }, + roomMemberModerationState: RoomMemberModerationState = aRoomMemberModerationState(), ) = RoomMemberListPresenter( room = joinedRoom, - roomMemberListDataSource = roomMemberListDataSource, coroutineDispatchers = coroutineDispatchers, - roomMembersModerationPresenter = roomMemberModerationPresenter, + roomMembersModerationPresenter = Presenter { + roomMemberModerationState + }, encryptionService = encryptedService, ) From d62f250e007552fd892571abf93014effdc12586 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 25 Nov 2025 14:30:59 +0100 Subject: [PATCH 09/11] quality: fix warnings --- .../roomdetails/impl/members/RoomMemberListEvents.kt | 2 +- .../roomdetails/impl/members/RoomMemberListPresenter.kt | 1 - .../features/roomdetails/impl/members/RoomMemberListView.kt | 6 ------ .../roomdetails/impl/members/RoomMemberListPresenterTest.kt | 1 - .../io/element/android/tests/konsist/KonsistPreviewTest.kt | 1 - 5 files changed, 1 insertion(+), 10 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt index a4b7b22bf3..ec1b130b3e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt @@ -11,7 +11,7 @@ package io.element.android.features.roomdetails.impl.members import io.element.android.libraries.matrix.api.room.RoomMember sealed interface RoomMemberListEvents { - data class ChangeSelectedSection(val section: SelectedSection): RoomMemberListEvents + data class ChangeSelectedSection(val section: SelectedSection) : RoomMemberListEvents data class UpdateSearchQuery(val query: String) : RoomMemberListEvents data class RoomMemberSelected(val roomMember: RoomMember) : RoomMemberListEvents } 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 90d85fac4b..f1d3f61f7e 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 @@ -52,7 +52,6 @@ class RoomMemberListPresenter( private val roomMembersModerationPresenter: Presenter, private val encryptionService: EncryptionService, ) : Presenter { - private val powerLevelRoomMemberComparator = PowerLevelRoomMemberComparator() @Composable diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt index d5a161b898..7c83f74f00 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt @@ -9,12 +9,6 @@ package io.element.android.features.roomdetails.impl.members import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.expandHorizontally -import androidx.compose.animation.expandVertically -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkHorizontally -import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt index 1548feae88..8d3d0e35e3 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt @@ -77,7 +77,6 @@ class RoomMemberListPresenterTest { val finalState = awaitItem() assertThat(finalState.selectedSection).isEqualTo(SelectedSection.MEMBERS) } - } @Test diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt index 4fbf8773db..b4f15d8d96 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt @@ -116,7 +116,6 @@ class KonsistPreviewTest { "ProgressDialogWithContentPreview", "ProgressDialogWithTextAndContentPreview", "ReadReceiptBottomSheetPreview", - "RoomMemberListViewBannedPreview", "SasEmojisPreview", "SecureBackupSetupViewChangePreview", "SelectedUserCannotRemovePreview", From 9a9cd7c0661763d8f567e4db6540599d2806ef57 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 25 Nov 2025 13:45:55 +0000 Subject: [PATCH 10/11] Update screenshots --- ...details.impl.members_RoomMemberListViewBanned_Day_0_en.png | 3 --- ...details.impl.members_RoomMemberListViewBanned_Day_1_en.png | 3 --- ...details.impl.members_RoomMemberListViewBanned_Day_2_en.png | 3 --- ...tails.impl.members_RoomMemberListViewBanned_Night_0_en.png | 3 --- ...tails.impl.members_RoomMemberListViewBanned_Night_1_en.png | 3 --- ...tails.impl.members_RoomMemberListViewBanned_Night_2_en.png | 3 --- ...s.roomdetails.impl.members_RoomMemberListView_Day_0_en.png | 4 ++-- ...s.roomdetails.impl.members_RoomMemberListView_Day_1_en.png | 4 ++-- ...s.roomdetails.impl.members_RoomMemberListView_Day_2_en.png | 4 ++-- ...s.roomdetails.impl.members_RoomMemberListView_Day_3_en.png | 4 ++-- ...s.roomdetails.impl.members_RoomMemberListView_Day_4_en.png | 4 ++-- ...s.roomdetails.impl.members_RoomMemberListView_Day_5_en.png | 4 ++-- ...s.roomdetails.impl.members_RoomMemberListView_Day_6_en.png | 4 ++-- ...s.roomdetails.impl.members_RoomMemberListView_Day_7_en.png | 3 --- ...s.roomdetails.impl.members_RoomMemberListView_Day_8_en.png | 3 --- ...s.roomdetails.impl.members_RoomMemberListView_Day_9_en.png | 3 --- ...roomdetails.impl.members_RoomMemberListView_Night_0_en.png | 4 ++-- ...roomdetails.impl.members_RoomMemberListView_Night_1_en.png | 4 ++-- ...roomdetails.impl.members_RoomMemberListView_Night_2_en.png | 4 ++-- ...roomdetails.impl.members_RoomMemberListView_Night_3_en.png | 4 ++-- ...roomdetails.impl.members_RoomMemberListView_Night_4_en.png | 4 ++-- ...roomdetails.impl.members_RoomMemberListView_Night_5_en.png | 4 ++-- ...roomdetails.impl.members_RoomMemberListView_Night_6_en.png | 4 ++-- ...roomdetails.impl.members_RoomMemberListView_Night_7_en.png | 3 --- ...roomdetails.impl.members_RoomMemberListView_Night_8_en.png | 3 --- ...roomdetails.impl.members_RoomMemberListView_Night_9_en.png | 3 --- ...embermoderation.impl_RoomMemberModerationView_Day_0_en.png | 4 ++-- ...embermoderation.impl_RoomMemberModerationView_Day_1_en.png | 4 ++-- ...embermoderation.impl_RoomMemberModerationView_Day_2_en.png | 4 ++-- ...embermoderation.impl_RoomMemberModerationView_Day_3_en.png | 4 ++-- ...bermoderation.impl_RoomMemberModerationView_Night_0_en.png | 4 ++-- ...bermoderation.impl_RoomMemberModerationView_Night_1_en.png | 4 ++-- ...bermoderation.impl_RoomMemberModerationView_Night_2_en.png | 4 ++-- ...bermoderation.impl_RoomMemberModerationView_Night_3_en.png | 4 ++-- ...stem.theme.components_SearchFieldsDark_Search_views_en.png | 3 +++ ...tem.theme.components_SearchFieldsLight_Search_views_en.png | 3 +++ 36 files changed, 50 insertions(+), 80 deletions(-) delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_7_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_8_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_9_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_7_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_8_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_9_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_SearchFieldsDark_Search_views_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_SearchFieldsLight_Search_views_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en.png deleted file mode 100644 index eeca901e19..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5070188463df330ea8f7969228f436dba0db5dd9eb9280f5b4484296176bf3e9 -size 11503 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en.png deleted file mode 100644 index f2cf45bfff..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4f544e2018c0cd221e6bba338e61af4f306809d10d37bd5b20c33e0fa67cbabb -size 11741 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en.png deleted file mode 100644 index eeca901e19..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5070188463df330ea8f7969228f436dba0db5dd9eb9280f5b4484296176bf3e9 -size 11503 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en.png deleted file mode 100644 index 1a42b8c5b7..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:07dc1c12e55e3eb3ce6ee01582535641a08d62d4b059bdd581af6c58063ca70c -size 10849 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en.png deleted file mode 100644 index 05a92bcc05..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1fe0b9379ed8bd9a3c19c7cff5eb6085657e1453e4f8d6ebe88a3338e738236c -size 11067 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en.png deleted file mode 100644 index 1a42b8c5b7..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:07dc1c12e55e3eb3ce6ee01582535641a08d62d4b059bdd581af6c58063ca70c -size 10849 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_0_en.png index 51c0e52a4a..0a2320e58a 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08172934ef864d57cb335b738dea8ad6f96ee2df4710c4f9d7e5adc3d110ad67 -size 44215 +oid sha256:367bc0df8b1839af2b434de55ea3815af4a8c112cc96e98ee76c3b9a4949fe52 +size 12985 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_1_en.png index 92584108e8..96ed928dbd 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad1fce13a5034b6db192a0735317b6cf5f10536ac05ccf1cc4996ad029ce9513 -size 49951 +oid sha256:1700a0f9e6f9115015849c900d7faa27f70fa29752bde7666eeb36cf4eb1a6da +size 19463 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_2_en.png index f2cf45bfff..c4d6e129cd 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f544e2018c0cd221e6bba338e61af4f306809d10d37bd5b20c33e0fa67cbabb -size 11741 +oid sha256:c8b5970490e96a693655df832f9c4bf83dd55a8b06da27b48881d9f61b2dfd9c +size 55110 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_3_en.png index 129f9d3f0d..6ac0644c32 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cad1aae139861f1ba83d3b03a6d78916c2ced5f19e6e99b935b4a49c1b5d12bb -size 12686 +oid sha256:bae192229d384047dcf5fb6744267a9e448d7f0a4f933a4984a0d1c3b9d07da2 +size 30789 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_4_en.png index f2cf45bfff..eff729cb56 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f544e2018c0cd221e6bba338e61af4f306809d10d37bd5b20c33e0fa67cbabb -size 11741 +oid sha256:6b7049a6865a0ea4529cc43d16a27b9107e26d6583f429affe8327840b84ac2a +size 56050 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_5_en.png index f653c35d7b..83c58ce783 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd50f1d35f872ee93bd6b2aff1feaffcc2314b11fae60302351f738bd86d735c -size 7630 +oid sha256:0b4a208d39a5f6040d17da5290a8f2f81cd917aa02c8e38b092fc79497e1e563 +size 18394 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_6_en.png index afa0af99d4..e75132eece 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c0c23f8cb90609cc1f6107f4d34efbc044de92286cb237487993953f5497e5e -size 6521 +oid sha256:acfaec83e840845137f9c07d1325e24f106bc67ccdbf6c548e77efe1d490596b +size 29428 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_7_en.png deleted file mode 100644 index a6d1f7bb95..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_7_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7f509f0c857c9d2c0a23657d213ea49961bb900b652edd116c4dcf29853c7776 -size 24336 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_8_en.png deleted file mode 100644 index 9df1642c6b..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_8_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:58d4c3e636f71ac1408189c3c14da3b0dd1e700a1be02a02f373adbcb5604b48 -size 10976 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_9_en.png deleted file mode 100644 index 42bcea1374..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Day_9_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cba49da736f96c2dd8ca3822e796930f4bd6ff3497b72f394d034147178906ce -size 18239 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_0_en.png index 2b024e7b08..aa53274f42 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd62c76e687e342f46b792958ddd3c36d44f5c603e4ddbe79f4fd2a8e41b9750 -size 44198 +oid sha256:7b01cfde0e59d64a9e6d576e22e2ac9eeab5ab1a99cb0f9f54d1c8cec80ac4bd +size 12154 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_1_en.png index 35fbc68769..56cecab8e5 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc7dd1459c04997170247296b34b9bd7953418361c2d72840d43aa159000a806 -size 49655 +oid sha256:2cb19c23879eb9698729561fb1ef79c1e65a36eab0f24681e4cf72dfdde45d04 +size 18281 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_2_en.png index 05a92bcc05..d4d2263960 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1fe0b9379ed8bd9a3c19c7cff5eb6085657e1453e4f8d6ebe88a3338e738236c -size 11067 +oid sha256:889734242ce96bfc206ae1421ab9c1cbccbcb11f99889445ea27833cbd057a0a +size 55231 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_3_en.png index 31ebaceceb..c313f14d52 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:34198a1ba6c0f6385eb8732d36622ee0d6f865c72085d95d378d82ed7afe9486 -size 11936 +oid sha256:dc20039c1c5d456f318f22e524360d411d4a4980b6a2a9312c0707c0fe865340 +size 29867 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_4_en.png index 05a92bcc05..dcd18cf977 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1fe0b9379ed8bd9a3c19c7cff5eb6085657e1453e4f8d6ebe88a3338e738236c -size 11067 +oid sha256:5bb86c6fea38d0e798cffa5fee0610a32048aeb1d3427cd9b5b749dfacee0e55 +size 56063 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_5_en.png index c002b74577..612b340865 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11c23632578610a78a6700f9d103ff6b4b3da03887cfb1918b10f2c6052e6820 -size 7549 +oid sha256:a6d55832abbcffc4d84b7139fbfb19d4c96a5ebcc9233ff139c5aae6b9f406d9 +size 17831 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_6_en.png index 775f637648..8b06d03eed 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b838359a59cdc357f0ef80d701f6093ea45875c7915ba438901748f43ce9106b -size 6316 +oid sha256:3802b661c6fc656f621ba3c368eeb93f004561b01926faa572c4ba5ee66d07eb +size 28474 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_7_en.png deleted file mode 100644 index f137d5539a..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_7_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bd6d96abbeda8ab8dd7804929ed205cd2c1eb8bb04d83d00a343387e93bc693b -size 24297 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_8_en.png deleted file mode 100644 index 8aa1b22c1d..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_8_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ced65352d0aee02e92f19fc890a8fa92efabfb8255a9a58e36dc539e2d4d6009 -size 10664 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_9_en.png deleted file mode 100644 index 083ca54d13..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.members_RoomMemberListView_Night_9_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d9f910e14188c705d993201bdebab2f818badc71272195aa4cce31728315beea -size 17207 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_0_en.png index 8c46960350..95a89c2a78 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54e526b8ab19ec121349dfc61f751b4d301c61ec47f1c1ec1c02d120b1f4faaf -size 17064 +oid sha256:f333d59f48ec9ff036e72c727c3bfae32dbb919c35b854906fca153bfc1c84e4 +size 17375 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_1_en.png index 20f1127aec..a99bf703be 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1be3ac8e92243baeceeb7349ed767e4c39ebfc4880b253e7f2e46ed9fc75da01 -size 20222 +oid sha256:dd83d242f9099de1ae0c84b07cb773e08dde1ae535ee2fd2bcb002e2e0e22132 +size 20397 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_2_en.png index 241888f87a..ea3e995ea4 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e8c820a8c4966526e7851b4f3152ccaf9072bbf8ebf21737d35d1c87325bf6f -size 22524 +oid sha256:0f3d63a5c888eb1b951be3c46c4c51f7c622d7e7838c35b3de388ac1cd20789d +size 22793 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_3_en.png index 5f0d9f5308..b23185f4fb 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:983e163f59ef03f5a7c30cf7073002105a663cec64f8a8d4fc89fa0991bfd730 -size 22646 +oid sha256:e5ce9706fbc30f9d99c35c9d1f145c7f933295fe3eb62063c640b303526649db +size 22915 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_0_en.png index bdfffdde8c..608faed7c0 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6db6ff988fd128f537764e5a3fcaa94e67db3057fc7030698adabe70f962c1b4 -size 15991 +oid sha256:06aad1478e9096e5215bd3116a7bef0e98037383c835de73f57d46a9cfa86137 +size 16415 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_1_en.png index 0920ebc642..d1328d932a 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4868e2ce56debe8b175715417190db0037d59d204a28b5c0bc0b0c81a5d91a9 -size 18925 +oid sha256:71cd9c85c33cc222836ec26f39e7209a954728d5beebadf53a784bd5a0813709 +size 19390 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_2_en.png index f888174764..b4c1e1cbee 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a59481d4391dd8d0cb749ff63d3bb34846b450a6713ade11a6dbbe0461bb3650 -size 21257 +oid sha256:66b527092681543f600725e508213f59524b5bdf3f3ceff2bbc47d43cc9d66e8 +size 21685 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_3_en.png index d2ee835a9f..1b47b493b4 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:381fbeb4b57f044fb785b65d04b041c4ca5efa89138742b8480a7540ed57b4f0 -size 21335 +oid sha256:47c1592948df21f0f3d69dead320f50e47ebf8da3bb86bc8302ad392d5813c57 +size 21765 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_SearchFieldsDark_Search_views_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_SearchFieldsDark_Search_views_en.png new file mode 100644 index 0000000000..c147670cee --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_SearchFieldsDark_Search_views_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39e0775e5c569a95653ab922084c4056635bec2056441771af3c8a3739a84901 +size 8143 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_SearchFieldsLight_Search_views_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_SearchFieldsLight_Search_views_en.png new file mode 100644 index 0000000000..21ed56ed1f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_SearchFieldsLight_Search_views_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a97489a6af869148d1daadb5948f26d00e888ecf0373e6ec45eb8a5ed8b12173 +size 8383 From 1e2ef2d96727dfc58eadffd8127ea2e529838e7f Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 25 Nov 2025 15:40:58 +0100 Subject: [PATCH 11/11] quality(test): fix test after AsyncData.map change --- .../space/impl/leave/LeaveSpacePresenterTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt index c99d3db361..495f0b38bd 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt @@ -62,7 +62,7 @@ class LeaveSpacePresenterTest { val state = awaitItem() assertThat(state.selectableSpaceRooms.isLoading()).isTrue() assertThat(state.leaveSpaceAction).isEqualTo(AsyncAction.Uninitialized) - skipItems(3) + skipItems(2) val stateError = awaitItem() assertThat(stateError.selectableSpaceRooms.isFailure()).isTrue() // Retry @@ -84,7 +84,7 @@ class LeaveSpacePresenterTest { presenter.test { val state = awaitItem() assertThat(state.spaceName).isNull() - skipItems(3) + skipItems(2) val finalState = awaitItem() assertThat(finalState.spaceName).isEqualTo(A_SPACE_NAME) assertThat(finalState.isLastAdmin).isTrue() @@ -120,7 +120,7 @@ class LeaveSpacePresenterTest { presenter.test { val state = awaitItem() assertThat(state.spaceName).isNull() - skipItems(3) + skipItems(2) val finalState = awaitItem() // The current state is not in the sub room list assertThat(finalState.selectableSpaceRooms.dataOrNull()!!.map { it.spaceRoom.roomId }).containsExactly(A_ROOM_ID, A_ROOM_ID_3) @@ -154,7 +154,7 @@ class LeaveSpacePresenterTest { ) ) presenter.test { - skipItems(4) + skipItems(3) val state = awaitItem() assertThat(state.spaceName).isNull() assertThat(state.isLastAdmin).isFalse() @@ -218,7 +218,7 @@ class LeaveSpacePresenterTest { ) ) presenter.test { - skipItems(4) + skipItems(3) val state = awaitItem() state.eventSink(LeaveSpaceEvents.LeaveSpace) val stateLeaving = awaitItem()