Merge pull request #2674 from element-hq/feature/bma/roomSuggestion
Room / User suggestions
This commit is contained in:
commit
dcf878350b
34 changed files with 573 additions and 53 deletions
|
|
@ -40,6 +40,7 @@ import io.element.android.libraries.architecture.NodeInputs
|
||||||
import io.element.android.libraries.architecture.inputs
|
import io.element.android.libraries.architecture.inputs
|
||||||
import io.element.android.libraries.di.DaggerComponentOwner
|
import io.element.android.libraries.di.DaggerComponentOwner
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
|
|
@ -61,6 +62,7 @@ class RoomLoadedFlowNode @AssistedInject constructor(
|
||||||
private val roomDetailsEntryPoint: RoomDetailsEntryPoint,
|
private val roomDetailsEntryPoint: RoomDetailsEntryPoint,
|
||||||
private val appNavigationStateService: AppNavigationStateService,
|
private val appNavigationStateService: AppNavigationStateService,
|
||||||
private val appCoroutineScope: CoroutineScope,
|
private val appCoroutineScope: CoroutineScope,
|
||||||
|
private val matrixClient: MatrixClient,
|
||||||
roomComponentFactory: RoomComponentFactory,
|
roomComponentFactory: RoomComponentFactory,
|
||||||
roomMembershipObserver: RoomMembershipObserver,
|
roomMembershipObserver: RoomMembershipObserver,
|
||||||
) : BaseFlowNode<RoomLoadedFlowNode.NavTarget>(
|
) : BaseFlowNode<RoomLoadedFlowNode.NavTarget>(
|
||||||
|
|
@ -92,6 +94,7 @@ class RoomLoadedFlowNode @AssistedInject constructor(
|
||||||
Timber.v("OnCreate => ${inputs.room.roomId}")
|
Timber.v("OnCreate => ${inputs.room.roomId}")
|
||||||
appNavigationStateService.onNavigateToRoom(id, inputs.room.roomId)
|
appNavigationStateService.onNavigateToRoom(id, inputs.room.roomId)
|
||||||
fetchRoomMembers()
|
fetchRoomMembers()
|
||||||
|
trackVisitedRoom()
|
||||||
},
|
},
|
||||||
onResume = {
|
onResume = {
|
||||||
appCoroutineScope.launch {
|
appCoroutineScope.launch {
|
||||||
|
|
@ -117,6 +120,10 @@ class RoomLoadedFlowNode @AssistedInject constructor(
|
||||||
inputs<Inputs>()
|
inputs<Inputs>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun trackVisitedRoom() = lifecycleScope.launch {
|
||||||
|
matrixClient.trackRecentlyVisitedRoom(inputs.room.roomId)
|
||||||
|
}
|
||||||
|
|
||||||
private fun fetchRoomMembers() = lifecycleScope.launch {
|
private fun fetchRoomMembers() = lifecycleScope.launch {
|
||||||
inputs.room.updateMembers()
|
inputs.room.updateMembers()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
|
||||||
import io.element.android.libraries.architecture.childNode
|
import io.element.android.libraries.architecture.childNode
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
|
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
@ -101,6 +102,7 @@ class RoomFlowNodeTest {
|
||||||
roomMembershipObserver = RoomMembershipObserver(),
|
roomMembershipObserver = RoomMembershipObserver(),
|
||||||
appCoroutineScope = coroutineScope,
|
appCoroutineScope = coroutineScope,
|
||||||
roomComponentFactory = FakeRoomComponentFactory(),
|
roomComponentFactory = FakeRoomComponentFactory(),
|
||||||
|
matrixClient = FakeMatrixClient(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
1
changelog.d/2634.misc
Normal file
1
changelog.d/2634.misc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Show users from last visited DM as suggestion when starting a Chat or when creating a Room.
|
||||||
|
|
@ -69,6 +69,8 @@ dependencies {
|
||||||
testImplementation(projects.libraries.usersearch.test)
|
testImplementation(projects.libraries.usersearch.test)
|
||||||
testImplementation(projects.features.createroom.test)
|
testImplementation(projects.features.createroom.test)
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
|
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||||
|
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
|
|
||||||
ksp(libs.showkase.processor)
|
ksp(libs.showkase.processor)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.createroom.impl.addpeople
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.createroom.impl.userlist.SelectionMode
|
import io.element.android.features.createroom.impl.userlist.SelectionMode
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListState
|
import io.element.android.features.createroom.impl.userlist.UserListState
|
||||||
|
import io.element.android.features.createroom.impl.userlist.aRecentDirectRoomList
|
||||||
import io.element.android.features.createroom.impl.userlist.aUserListState
|
import io.element.android.features.createroom.impl.userlist.aUserListState
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||||
|
|
@ -29,13 +30,13 @@ open class AddPeopleUserListStateProvider : PreviewParameterProvider<UserListSta
|
||||||
override val values: Sequence<UserListState>
|
override val values: Sequence<UserListState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aUserListState(),
|
aUserListState(),
|
||||||
aUserListState().copy(
|
aUserListState(
|
||||||
searchResults = SearchBarResultState.Results(aMatrixUserList().toImmutableList()),
|
searchResults = SearchBarResultState.Results(aMatrixUserList().toImmutableList()),
|
||||||
selectedUsers = aMatrixUserList().toImmutableList(),
|
selectedUsers = aMatrixUserList().toImmutableList(),
|
||||||
isSearchActive = false,
|
isSearchActive = false,
|
||||||
selectionMode = SelectionMode.Multiple,
|
selectionMode = SelectionMode.Multiple,
|
||||||
),
|
),
|
||||||
aUserListState().copy(
|
aUserListState(
|
||||||
searchResults = SearchBarResultState.Results(
|
searchResults = SearchBarResultState.Results(
|
||||||
aMatrixUserList()
|
aMatrixUserList()
|
||||||
.mapIndexed { index, matrixUser ->
|
.mapIndexed { index, matrixUser ->
|
||||||
|
|
@ -46,6 +47,9 @@ open class AddPeopleUserListStateProvider : PreviewParameterProvider<UserListSta
|
||||||
selectedUsers = aMatrixUserList().toImmutableList(),
|
selectedUsers = aMatrixUserList().toImmutableList(),
|
||||||
isSearchActive = true,
|
isSearchActive = true,
|
||||||
selectionMode = SelectionMode.Multiple,
|
selectionMode = SelectionMode.Multiple,
|
||||||
)
|
),
|
||||||
|
aUserListState(
|
||||||
|
recentDirectRooms = aRecentDirectRoomList(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,8 @@
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.addpeople
|
package io.element.android.features.createroom.impl.addpeople
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -64,21 +62,16 @@ fun AddPeopleView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
Column(
|
UserListView(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.consumeWindowInsets(padding),
|
.consumeWindowInsets(padding),
|
||||||
) {
|
state = state,
|
||||||
UserListView(
|
showBackButton = false,
|
||||||
modifier = Modifier
|
onUserSelected = {},
|
||||||
.fillMaxWidth(),
|
onUserDeselected = {},
|
||||||
state = state,
|
)
|
||||||
showBackButton = false,
|
|
||||||
onUserSelected = { },
|
|
||||||
onUserDeselected = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,27 @@ package io.element.android.features.createroom.impl.components
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListEvents
|
import io.element.android.features.createroom.impl.userlist.UserListEvents
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListState
|
import io.element.android.features.createroom.impl.userlist.UserListState
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListStateProvider
|
import io.element.android.features.createroom.impl.userlist.UserListStateProvider
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.ListSectionHeader
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
import io.element.android.libraries.matrix.ui.components.CheckableUserRow
|
||||||
|
import io.element.android.libraries.matrix.ui.components.CheckableUserRowData
|
||||||
import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList
|
import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList
|
||||||
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
|
import io.element.android.libraries.matrix.ui.model.getBestName
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UserListView(
|
fun UserListView(
|
||||||
|
|
@ -74,6 +84,43 @@ fun UserListView(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (!state.isSearchActive && state.recentDirectRooms.isNotEmpty()) {
|
||||||
|
LazyColumn {
|
||||||
|
item {
|
||||||
|
ListSectionHeader(
|
||||||
|
title = stringResource(id = CommonStrings.common_suggestions),
|
||||||
|
hasDivider = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
state.recentDirectRooms.forEachIndexed { index, recentDirectRoom ->
|
||||||
|
item {
|
||||||
|
val isSelected = state.selectedUsers.any {
|
||||||
|
recentDirectRoom.matrixUser.userId == it.userId
|
||||||
|
}
|
||||||
|
CheckableUserRow(
|
||||||
|
checked = isSelected,
|
||||||
|
onCheckedChange = {
|
||||||
|
if (isSelected) {
|
||||||
|
state.eventSink(UserListEvents.RemoveFromSelection(recentDirectRoom.matrixUser))
|
||||||
|
onUserDeselected(recentDirectRoom.matrixUser)
|
||||||
|
} else {
|
||||||
|
state.eventSink(UserListEvents.AddToSelection(recentDirectRoom.matrixUser))
|
||||||
|
onUserSelected(recentDirectRoom.matrixUser)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data = CheckableUserRowData.Resolved(
|
||||||
|
avatarData = recentDirectRoom.matrixUser.getAvatarData(AvatarSize.UserListItem),
|
||||||
|
name = recentDirectRoom.matrixUser.getBestName(),
|
||||||
|
subtext = recentDirectRoom.matrixUser.userId.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (index < state.recentDirectRooms.lastIndex) {
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,12 @@
|
||||||
package io.element.android.features.createroom.impl.root
|
package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.features.createroom.impl.userlist.UserListState
|
||||||
|
import io.element.android.features.createroom.impl.userlist.aRecentDirectRoomList
|
||||||
import io.element.android.features.createroom.impl.userlist.aUserListState
|
import io.element.android.features.createroom.impl.userlist.aUserListState
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||||
import io.element.android.libraries.usersearch.api.UserSearchResult
|
import io.element.android.libraries.usersearch.api.UserSearchResult
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -28,7 +31,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
|
||||||
override val values: Sequence<CreateRoomRootState>
|
override val values: Sequence<CreateRoomRootState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aCreateRoomRootState(),
|
aCreateRoomRootState(),
|
||||||
aCreateRoomRootState().copy(
|
aCreateRoomRootState(
|
||||||
startDmAction = AsyncAction.Loading,
|
startDmAction = AsyncAction.Loading,
|
||||||
userListState = aMatrixUser().let {
|
userListState = aMatrixUser().let {
|
||||||
aUserListState().copy(
|
aUserListState().copy(
|
||||||
|
|
@ -39,7 +42,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
aCreateRoomRootState().copy(
|
aCreateRoomRootState(
|
||||||
startDmAction = AsyncAction.Failure(Throwable("error")),
|
startDmAction = AsyncAction.Failure(Throwable("error")),
|
||||||
userListState = aMatrixUser().let {
|
userListState = aMatrixUser().let {
|
||||||
aUserListState().copy(
|
aUserListState().copy(
|
||||||
|
|
@ -50,12 +53,22 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
aCreateRoomRootState(
|
||||||
|
userListState = aUserListState(
|
||||||
|
recentDirectRooms = aRecentDirectRoomList()
|
||||||
|
)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aCreateRoomRootState() = CreateRoomRootState(
|
fun aCreateRoomRootState(
|
||||||
eventSink = {},
|
applicationName: String = "Element X Preview",
|
||||||
applicationName = "Element X Preview",
|
userListState: UserListState = aUserListState(),
|
||||||
startDmAction = AsyncAction.Uninitialized,
|
startDmAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||||
userListState = aUserListState(),
|
eventSink: (CreateRoomRootEvents) -> Unit = {},
|
||||||
|
) = CreateRoomRootState(
|
||||||
|
applicationName = applicationName,
|
||||||
|
userListState = userListState,
|
||||||
|
startDmAction = startDmAction,
|
||||||
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -46,11 +47,14 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.ListSectionHeader
|
||||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CreateRoomRootView(
|
fun CreateRoomRootView(
|
||||||
|
|
@ -77,7 +81,11 @@ fun CreateRoomRootView(
|
||||||
) {
|
) {
|
||||||
UserListView(
|
UserListView(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
state = state.userListState,
|
// Do not render suggestions in this case, the suggestion will be rendered
|
||||||
|
// by CreateRoomActionButtonsList
|
||||||
|
state = state.userListState.copy(
|
||||||
|
recentDirectRooms = persistentListOf(),
|
||||||
|
),
|
||||||
onUserSelected = {
|
onUserSelected = {
|
||||||
state.eventSink(CreateRoomRootEvents.StartDM(it))
|
state.eventSink(CreateRoomRootEvents.StartDM(it))
|
||||||
},
|
},
|
||||||
|
|
@ -89,6 +97,7 @@ fun CreateRoomRootView(
|
||||||
state = state,
|
state = state,
|
||||||
onNewRoomClicked = onNewRoomClicked,
|
onNewRoomClicked = onNewRoomClicked,
|
||||||
onInvitePeopleClicked = onInviteFriendsClicked,
|
onInvitePeopleClicked = onInviteFriendsClicked,
|
||||||
|
onDmClicked = onOpenDM,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +115,7 @@ fun CreateRoomRootView(
|
||||||
onRetry = {
|
onRetry = {
|
||||||
state.userListState.selectedUsers.firstOrNull()
|
state.userListState.selectedUsers.firstOrNull()
|
||||||
?.let { state.eventSink(CreateRoomRootEvents.StartDM(it)) }
|
?.let { state.eventSink(CreateRoomRootEvents.StartDM(it)) }
|
||||||
// Cancel start DM if there is no more selected user (should not happen)
|
// Cancel start DM if there is no more selected user (should not happen)
|
||||||
?: state.eventSink(CreateRoomRootEvents.CancelStartDM)
|
?: state.eventSink(CreateRoomRootEvents.CancelStartDM)
|
||||||
},
|
},
|
||||||
onErrorDismiss = { state.eventSink(CreateRoomRootEvents.CancelStartDM) },
|
onErrorDismiss = { state.eventSink(CreateRoomRootEvents.CancelStartDM) },
|
||||||
|
|
@ -139,18 +148,43 @@ private fun CreateRoomActionButtonsList(
|
||||||
state: CreateRoomRootState,
|
state: CreateRoomRootState,
|
||||||
onNewRoomClicked: () -> Unit,
|
onNewRoomClicked: () -> Unit,
|
||||||
onInvitePeopleClicked: () -> Unit,
|
onInvitePeopleClicked: () -> Unit,
|
||||||
|
onDmClicked: (RoomId) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column {
|
LazyColumn {
|
||||||
CreateRoomActionButton(
|
item {
|
||||||
iconRes = CompoundDrawables.ic_compound_plus,
|
CreateRoomActionButton(
|
||||||
text = stringResource(id = R.string.screen_create_room_action_create_room),
|
iconRes = CompoundDrawables.ic_compound_plus,
|
||||||
onClick = onNewRoomClicked,
|
text = stringResource(id = R.string.screen_create_room_action_create_room),
|
||||||
)
|
onClick = onNewRoomClicked,
|
||||||
CreateRoomActionButton(
|
)
|
||||||
iconRes = CompoundDrawables.ic_compound_share_android,
|
}
|
||||||
text = stringResource(id = CommonStrings.action_invite_friends_to_app, state.applicationName),
|
item {
|
||||||
onClick = onInvitePeopleClicked,
|
CreateRoomActionButton(
|
||||||
)
|
iconRes = CompoundDrawables.ic_compound_share_android,
|
||||||
|
text = stringResource(id = CommonStrings.action_invite_friends_to_app, state.applicationName),
|
||||||
|
onClick = onInvitePeopleClicked,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (state.userListState.recentDirectRooms.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
ListSectionHeader(
|
||||||
|
title = stringResource(id = CommonStrings.common_suggestions),
|
||||||
|
hasDivider = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
state.userListState.recentDirectRooms.forEach { recentDirectRoom ->
|
||||||
|
item {
|
||||||
|
MatrixUserRow(
|
||||||
|
modifier = Modifier.clickable(
|
||||||
|
onClick = {
|
||||||
|
onDmClicked(recentDirectRoom.roomId)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
matrixUser = recentDirectRoom.matrixUser,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.matrix.api.room.recent.RecentDirectRoom
|
||||||
|
import io.element.android.libraries.matrix.api.room.recent.getRecentDirectRooms
|
||||||
import io.element.android.libraries.usersearch.api.UserRepository
|
import io.element.android.libraries.usersearch.api.UserRepository
|
||||||
import io.element.android.libraries.usersearch.api.UserSearchResult
|
import io.element.android.libraries.usersearch.api.UserSearchResult
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
@ -41,6 +44,7 @@ class DefaultUserListPresenter @AssistedInject constructor(
|
||||||
@Assisted val args: UserListPresenterArgs,
|
@Assisted val args: UserListPresenterArgs,
|
||||||
@Assisted val userRepository: UserRepository,
|
@Assisted val userRepository: UserRepository,
|
||||||
@Assisted val userListDataStore: UserListDataStore,
|
@Assisted val userListDataStore: UserListDataStore,
|
||||||
|
private val matrixClient: MatrixClient,
|
||||||
) : UserListPresenter {
|
) : UserListPresenter {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
@ContributesBinding(SessionScope::class)
|
@ContributesBinding(SessionScope::class)
|
||||||
|
|
@ -54,6 +58,10 @@ class DefaultUserListPresenter @AssistedInject constructor(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): UserListState {
|
override fun present(): UserListState {
|
||||||
|
var recentDirectRooms by remember { mutableStateOf(emptyList<RecentDirectRoom>()) }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
recentDirectRooms = matrixClient.getRecentDirectRooms()
|
||||||
|
}
|
||||||
var isSearchActive by rememberSaveable { mutableStateOf(false) }
|
var isSearchActive by rememberSaveable { mutableStateOf(false) }
|
||||||
val selectedUsers by userListDataStore.selectedUsers().collectAsState(emptyList())
|
val selectedUsers by userListDataStore.selectedUsers().collectAsState(emptyList())
|
||||||
var searchQuery by rememberSaveable { mutableStateOf("") }
|
var searchQuery by rememberSaveable { mutableStateOf("") }
|
||||||
|
|
@ -82,6 +90,7 @@ class DefaultUserListPresenter @AssistedInject constructor(
|
||||||
isSearchActive = isSearchActive,
|
isSearchActive = isSearchActive,
|
||||||
showSearchLoader = showSearchLoader,
|
showSearchLoader = showSearchLoader,
|
||||||
selectionMode = args.selectionMode,
|
selectionMode = args.selectionMode,
|
||||||
|
recentDirectRooms = recentDirectRooms.toImmutableList(),
|
||||||
eventSink = { event ->
|
eventSink = { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
is UserListEvents.OnSearchActiveChanged -> isSearchActive = event.active
|
is UserListEvents.OnSearchActiveChanged -> isSearchActive = event.active
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.features.createroom.impl.userlist
|
package io.element.android.features.createroom.impl.userlist
|
||||||
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
import io.element.android.libraries.matrix.api.room.recent.RecentDirectRoom
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.usersearch.api.UserSearchResult
|
import io.element.android.libraries.usersearch.api.UserSearchResult
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
@ -28,6 +29,7 @@ data class UserListState(
|
||||||
val selectedUsers: ImmutableList<MatrixUser>,
|
val selectedUsers: ImmutableList<MatrixUser>,
|
||||||
val isSearchActive: Boolean,
|
val isSearchActive: Boolean,
|
||||||
val selectionMode: SelectionMode,
|
val selectionMode: SelectionMode,
|
||||||
|
val recentDirectRooms: ImmutableList<RecentDirectRoom>,
|
||||||
val eventSink: (UserListEvents) -> Unit,
|
val eventSink: (UserListEvents) -> Unit,
|
||||||
) {
|
) {
|
||||||
val isMultiSelectionEnabled = selectionMode == SelectionMode.Multiple
|
val isMultiSelectionEnabled = selectionMode == SelectionMode.Multiple
|
||||||
|
|
|
||||||
|
|
@ -18,54 +18,82 @@ package io.element.android.features.createroom.impl.userlist
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.room.recent.RecentDirectRoom
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||||
import io.element.android.libraries.usersearch.api.UserSearchResult
|
import io.element.android.libraries.usersearch.api.UserSearchResult
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
open class UserListStateProvider : PreviewParameterProvider<UserListState> {
|
open class UserListStateProvider : PreviewParameterProvider<UserListState> {
|
||||||
override val values: Sequence<UserListState>
|
override val values: Sequence<UserListState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aUserListState(),
|
aUserListState(),
|
||||||
aUserListState().copy(
|
aUserListState(
|
||||||
isSearchActive = false,
|
isSearchActive = false,
|
||||||
selectedUsers = aListOfSelectedUsers(),
|
selectedUsers = aListOfSelectedUsers(),
|
||||||
selectionMode = SelectionMode.Multiple,
|
selectionMode = SelectionMode.Multiple,
|
||||||
),
|
),
|
||||||
aUserListState().copy(isSearchActive = true),
|
aUserListState(isSearchActive = true),
|
||||||
aUserListState().copy(isSearchActive = true, searchQuery = "someone"),
|
aUserListState(isSearchActive = true, searchQuery = "someone"),
|
||||||
aUserListState().copy(isSearchActive = true, searchQuery = "someone", selectionMode = SelectionMode.Multiple),
|
aUserListState(isSearchActive = true, searchQuery = "someone", selectionMode = SelectionMode.Multiple),
|
||||||
aUserListState().copy(
|
aUserListState(
|
||||||
isSearchActive = true,
|
isSearchActive = true,
|
||||||
searchQuery = "@someone:matrix.org",
|
searchQuery = "@someone:matrix.org",
|
||||||
selectedUsers = aMatrixUserList().toImmutableList(),
|
selectedUsers = aMatrixUserList().toImmutableList(),
|
||||||
searchResults = SearchBarResultState.Results(aListOfUserSearchResults()),
|
searchResults = SearchBarResultState.Results(aListOfUserSearchResults()),
|
||||||
),
|
),
|
||||||
aUserListState().copy(
|
aUserListState(
|
||||||
isSearchActive = true,
|
isSearchActive = true,
|
||||||
searchQuery = "@someone:matrix.org",
|
searchQuery = "@someone:matrix.org",
|
||||||
selectionMode = SelectionMode.Multiple,
|
selectionMode = SelectionMode.Multiple,
|
||||||
selectedUsers = aMatrixUserList().toImmutableList(),
|
selectedUsers = aMatrixUserList().toImmutableList(),
|
||||||
searchResults = SearchBarResultState.Results(aListOfUserSearchResults()),
|
searchResults = SearchBarResultState.Results(aListOfUserSearchResults()),
|
||||||
),
|
),
|
||||||
aUserListState().copy(
|
aUserListState(
|
||||||
isSearchActive = true,
|
isSearchActive = true,
|
||||||
searchQuery = "something-with-no-results",
|
searchQuery = "something-with-no-results",
|
||||||
searchResults = SearchBarResultState.NoResultsFound()
|
searchResults = SearchBarResultState.NoResultsFound()
|
||||||
),
|
),
|
||||||
aUserListState().copy(isSearchActive = true, searchQuery = "someone", selectionMode = SelectionMode.Single),
|
aUserListState(
|
||||||
|
isSearchActive = true,
|
||||||
|
searchQuery = "someone",
|
||||||
|
selectionMode = SelectionMode.Single,
|
||||||
|
),
|
||||||
|
aUserListState(
|
||||||
|
recentDirectRooms = aRecentDirectRoomList(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aUserListState() = UserListState(
|
fun aUserListState(
|
||||||
isSearchActive = false,
|
searchQuery: String = "",
|
||||||
searchQuery = "",
|
isSearchActive: Boolean = false,
|
||||||
searchResults = SearchBarResultState.Initial(),
|
searchResults: SearchBarResultState<ImmutableList<UserSearchResult>> = SearchBarResultState.Initial(),
|
||||||
selectedUsers = persistentListOf(),
|
selectedUsers: List<MatrixUser> = emptyList(),
|
||||||
selectionMode = SelectionMode.Single,
|
showSearchLoader: Boolean = false,
|
||||||
showSearchLoader = false,
|
selectionMode: SelectionMode = SelectionMode.Single,
|
||||||
eventSink = {}
|
recentDirectRooms: List<RecentDirectRoom> = emptyList(),
|
||||||
|
eventSink: (UserListEvents) -> Unit = {},
|
||||||
|
) = UserListState(
|
||||||
|
searchQuery = searchQuery,
|
||||||
|
isSearchActive = isSearchActive,
|
||||||
|
searchResults = searchResults,
|
||||||
|
selectedUsers = selectedUsers.toImmutableList(),
|
||||||
|
showSearchLoader = showSearchLoader,
|
||||||
|
selectionMode = selectionMode,
|
||||||
|
recentDirectRooms = recentDirectRooms.toImmutableList(),
|
||||||
|
eventSink = eventSink
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aListOfSelectedUsers() = aMatrixUserList().take(6).toImmutableList()
|
fun aListOfSelectedUsers() = aMatrixUserList().take(6).toImmutableList()
|
||||||
fun aListOfUserSearchResults() = aMatrixUserList().take(6).map { UserSearchResult(it) }.toImmutableList()
|
fun aListOfUserSearchResults() = aMatrixUserList().take(6).map { UserSearchResult(it) }.toImmutableList()
|
||||||
|
|
||||||
|
fun aRecentDirectRoomList(
|
||||||
|
count: Int = 5
|
||||||
|
): List<RecentDirectRoom> = aMatrixUserList()
|
||||||
|
.take(count)
|
||||||
|
.map {
|
||||||
|
RecentDirectRoom(RoomId("!aRoom:id"), it)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.createroom.impl.addpeople
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.element.android.features.createroom.impl.userlist.UserListEvents
|
||||||
|
import io.element.android.features.createroom.impl.userlist.UserListState
|
||||||
|
import io.element.android.features.createroom.impl.userlist.aUserListState
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
|
import io.element.android.tests.testutils.clickOn
|
||||||
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
|
import io.element.android.tests.testutils.pressBack
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class AddPeopleViewTest {
|
||||||
|
@get:Rule
|
||||||
|
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking on back invokes the expected callback`() {
|
||||||
|
val eventsRecorder = EventsRecorder<UserListEvents>()
|
||||||
|
ensureCalledOnce {
|
||||||
|
rule.setAddPeopleView(
|
||||||
|
aUserListState(
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
onBackPressed = it
|
||||||
|
)
|
||||||
|
rule.pressBack()
|
||||||
|
}
|
||||||
|
eventsRecorder.assertSingle(UserListEvents.UpdateSearchQuery(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking on back during search emits the expected Event`() {
|
||||||
|
val eventsRecorder = EventsRecorder<UserListEvents>()
|
||||||
|
rule.setAddPeopleView(
|
||||||
|
aUserListState(
|
||||||
|
isSearchActive = true,
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
rule.pressBack()
|
||||||
|
eventsRecorder.assertSingle(UserListEvents.OnSearchActiveChanged(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking on skip invokes the expected callback`() {
|
||||||
|
val eventsRecorder = EventsRecorder<UserListEvents>()
|
||||||
|
ensureCalledOnce {
|
||||||
|
rule.setAddPeopleView(
|
||||||
|
aUserListState(
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
onNextPressed = it
|
||||||
|
)
|
||||||
|
rule.clickOn(CommonStrings.action_skip)
|
||||||
|
}
|
||||||
|
eventsRecorder.assertSingle(UserListEvents.UpdateSearchQuery(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAddPeopleView(
|
||||||
|
state: UserListState,
|
||||||
|
onBackPressed: () -> Unit = EnsureNeverCalled(),
|
||||||
|
onNextPressed: () -> Unit = EnsureNeverCalled(),
|
||||||
|
) {
|
||||||
|
setContent {
|
||||||
|
AddPeopleView(
|
||||||
|
state = state,
|
||||||
|
onBackPressed = onBackPressed,
|
||||||
|
onNextPressed = onNextPressed,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.element.android.features.createroom.impl.R
|
||||||
|
import io.element.android.features.createroom.impl.userlist.aRecentDirectRoomList
|
||||||
|
import io.element.android.features.createroom.impl.userlist.aUserListState
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.ui.model.getBestName
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
|
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||||
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
|
import io.element.android.tests.testutils.clickOn
|
||||||
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
|
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
|
import io.element.android.tests.testutils.pressBack
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class CreateRoomRootViewTest {
|
||||||
|
@get:Rule
|
||||||
|
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking on back invokes the expected callback`() {
|
||||||
|
val eventsRecorder = EventsRecorder<CreateRoomRootEvents>(expectEvents = false)
|
||||||
|
ensureCalledOnce {
|
||||||
|
rule.setCreateRoomRootView(
|
||||||
|
aCreateRoomRootState(
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
onClosePressed = it
|
||||||
|
)
|
||||||
|
rule.pressBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking on New room invokes the expected callback`() {
|
||||||
|
val eventsRecorder = EventsRecorder<CreateRoomRootEvents>(expectEvents = false)
|
||||||
|
ensureCalledOnce {
|
||||||
|
rule.setCreateRoomRootView(
|
||||||
|
aCreateRoomRootState(
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
onNewRoomClicked = it
|
||||||
|
)
|
||||||
|
rule.clickOn(R.string.screen_create_room_action_create_room)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Config(qualifiers = "h1024dp")
|
||||||
|
@Test
|
||||||
|
fun `clicking on Invite people invokes the expected callback`() {
|
||||||
|
val eventsRecorder = EventsRecorder<CreateRoomRootEvents>(expectEvents = false)
|
||||||
|
ensureCalledOnce {
|
||||||
|
rule.setCreateRoomRootView(
|
||||||
|
aCreateRoomRootState(
|
||||||
|
applicationName = "test",
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
onInviteFriendsClicked = it
|
||||||
|
)
|
||||||
|
val text = rule.activity.getString(CommonStrings.action_invite_friends_to_app, "test")
|
||||||
|
rule.onNodeWithText(text).performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Config(qualifiers = "h1024dp")
|
||||||
|
@Test
|
||||||
|
fun `clicking on a user suggestion invokes the expected callback`() {
|
||||||
|
val recentDirectRoomList = aRecentDirectRoomList()
|
||||||
|
val firstRoom = recentDirectRoomList[0]
|
||||||
|
val eventsRecorder = EventsRecorder<CreateRoomRootEvents>(expectEvents = false)
|
||||||
|
ensureCalledOnceWithParam(firstRoom.roomId) {
|
||||||
|
rule.setCreateRoomRootView(
|
||||||
|
aCreateRoomRootState(
|
||||||
|
userListState = aUserListState(
|
||||||
|
recentDirectRooms = recentDirectRoomList
|
||||||
|
),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
onOpenDM = it
|
||||||
|
)
|
||||||
|
rule.onNodeWithText(firstRoom.matrixUser.getBestName()).performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setCreateRoomRootView(
|
||||||
|
state: CreateRoomRootState,
|
||||||
|
onClosePressed: () -> Unit = EnsureNeverCalled(),
|
||||||
|
onNewRoomClicked: () -> Unit = EnsureNeverCalled(),
|
||||||
|
onOpenDM: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
|
onInviteFriendsClicked: () -> Unit = EnsureNeverCalled(),
|
||||||
|
) {
|
||||||
|
setContent {
|
||||||
|
CreateRoomRootView(
|
||||||
|
state = state,
|
||||||
|
onClosePressed = onClosePressed,
|
||||||
|
onNewRoomClicked = onNewRoomClicked,
|
||||||
|
onOpenDM = onOpenDM,
|
||||||
|
onInviteFriendsClicked = onInviteFriendsClicked,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||||
import io.element.android.libraries.usersearch.api.UserSearchResult
|
import io.element.android.libraries.usersearch.api.UserSearchResult
|
||||||
|
|
@ -45,6 +46,7 @@ class DefaultUserListPresenterTests {
|
||||||
UserListPresenterArgs(selectionMode = SelectionMode.Single),
|
UserListPresenterArgs(selectionMode = SelectionMode.Single),
|
||||||
userRepository,
|
userRepository,
|
||||||
UserListDataStore(),
|
UserListDataStore(),
|
||||||
|
FakeMatrixClient(),
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -66,6 +68,7 @@ class DefaultUserListPresenterTests {
|
||||||
UserListPresenterArgs(selectionMode = SelectionMode.Multiple),
|
UserListPresenterArgs(selectionMode = SelectionMode.Multiple),
|
||||||
userRepository,
|
userRepository,
|
||||||
UserListDataStore(),
|
UserListDataStore(),
|
||||||
|
FakeMatrixClient(),
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -87,6 +90,7 @@ class DefaultUserListPresenterTests {
|
||||||
UserListPresenterArgs(selectionMode = SelectionMode.Single),
|
UserListPresenterArgs(selectionMode = SelectionMode.Single),
|
||||||
userRepository,
|
userRepository,
|
||||||
UserListDataStore(),
|
UserListDataStore(),
|
||||||
|
FakeMatrixClient(),
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -123,6 +127,7 @@ class DefaultUserListPresenterTests {
|
||||||
),
|
),
|
||||||
userRepository,
|
userRepository,
|
||||||
UserListDataStore(),
|
UserListDataStore(),
|
||||||
|
FakeMatrixClient(),
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -175,6 +180,7 @@ class DefaultUserListPresenterTests {
|
||||||
),
|
),
|
||||||
userRepository,
|
userRepository,
|
||||||
UserListDataStore(),
|
UserListDataStore(),
|
||||||
|
FakeMatrixClient(),
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
@ -200,6 +206,7 @@ class DefaultUserListPresenterTests {
|
||||||
UserListPresenterArgs(selectionMode = SelectionMode.Single),
|
UserListPresenterArgs(selectionMode = SelectionMode.Single),
|
||||||
userRepository,
|
userRepository,
|
||||||
UserListDataStore(),
|
UserListDataStore(),
|
||||||
|
FakeMatrixClient(),
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import io.element.android.features.messages.impl.R
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
|
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
|
||||||
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
|
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions
|
import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions
|
||||||
|
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
|
@ -166,6 +167,7 @@ internal fun TimelineItemReactionsViewOutgoingPreview() = ElementPreview {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExcludeFromCoverage
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContentToPreview(
|
private fun ContentToPreview(
|
||||||
reactions: ImmutableList<AggregatedReaction>,
|
reactions: ImmutableList<AggregatedReaction>,
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
import io.element.android.features.preferences.impl.user.UserPreferences
|
import io.element.android.features.preferences.impl.user.UserPreferences
|
||||||
|
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
|
|
@ -212,6 +213,7 @@ internal fun PreferencesRootViewLightPreview(@PreviewParameter(MatrixUserProvide
|
||||||
internal fun PreferencesRootViewDarkPreview(@PreviewParameter(MatrixUserProvider::class) matrixUser: MatrixUser) =
|
internal fun PreferencesRootViewDarkPreview(@PreviewParameter(MatrixUserProvider::class) matrixUser: MatrixUser) =
|
||||||
ElementPreviewDark { ContentToPreview(matrixUser) }
|
ElementPreviewDark { ContentToPreview(matrixUser) }
|
||||||
|
|
||||||
|
@ExcludeFromCoverage
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContentToPreview(matrixUser: MatrixUser) {
|
private fun ContentToPreview(matrixUser: MatrixUser) {
|
||||||
PreferencesRootView(
|
PreferencesRootView(
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.roomdetails.impl.R
|
import io.element.android.features.roomdetails.impl.R
|
||||||
import io.element.android.features.roomdetails.impl.blockuser.BlockUserDialogs
|
import io.element.android.features.roomdetails.impl.blockuser.BlockUserDialogs
|
||||||
import io.element.android.features.roomdetails.impl.blockuser.BlockUserSection
|
import io.element.android.features.roomdetails.impl.blockuser.BlockUserSection
|
||||||
|
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults
|
import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
|
|
@ -127,6 +128,7 @@ internal fun RoomMemberDetailsViewLightPreview(@PreviewParameter(RoomMemberDetai
|
||||||
internal fun RoomMemberDetailsViewDarkPreview(@PreviewParameter(RoomMemberDetailsStateProvider::class) state: RoomMemberDetailsState) =
|
internal fun RoomMemberDetailsViewDarkPreview(@PreviewParameter(RoomMemberDetailsStateProvider::class) state: RoomMemberDetailsState) =
|
||||||
ElementPreviewDark { ContentToPreview(state) }
|
ElementPreviewDark { ContentToPreview(state) }
|
||||||
|
|
||||||
|
@ExcludeFromCoverage
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContentToPreview(state: RoomMemberDetailsState) {
|
private fun ContentToPreview(state: RoomMemberDetailsState) {
|
||||||
RoomMemberDetailsView(
|
RoomMemberDetailsView(
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||||
import io.element.android.libraries.designsystem.R
|
import io.element.android.libraries.designsystem.R
|
||||||
import io.element.android.libraries.designsystem.modifiers.blurCompat
|
import io.element.android.libraries.designsystem.modifiers.blurCompat
|
||||||
import io.element.android.libraries.designsystem.modifiers.blurredShapeShadow
|
import io.element.android.libraries.designsystem.modifiers.blurredShapeShadow
|
||||||
|
|
@ -171,6 +172,7 @@ internal fun ElementLogoAtomLargeNoBlurShadowPreview() = ElementPreview {
|
||||||
ContentToPreview(ElementLogoAtomSize.Large, useBlurredShadow = false)
|
ContentToPreview(ElementLogoAtomSize.Large, useBlurredShadow = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExcludeFromCoverage
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContentToPreview(elementLogoAtomSize: ElementLogoAtomSize, useBlurredShadow: Boolean = true) {
|
private fun ContentToPreview(elementLogoAtomSize: ElementLogoAtomSize, useBlurredShadow: Boolean = true) {
|
||||||
Box(
|
Box(
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
|
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
|
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
|
||||||
import io.element.android.libraries.designsystem.components.preferences.components.PreferenceIcon
|
import io.element.android.libraries.designsystem.components.preferences.components.PreferenceIcon
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
|
|
@ -162,6 +163,7 @@ internal fun PreferenceTextWithEndBadgeDarkPreview() = ElementPreviewDark {
|
||||||
ContentToPreview(showEndBadge = true)
|
ContentToPreview(showEndBadge = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExcludeFromCoverage
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContentToPreview(showEndBadge: Boolean) {
|
private fun ContentToPreview(showEndBadge: Boolean) {
|
||||||
Column(
|
Column(
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
|
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||||
|
|
@ -269,6 +270,7 @@ internal fun SearchBarActiveWithContentPreview() = ElementThemedPreview {
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
@ExcludeFromCoverage
|
||||||
private fun ContentToPreview(
|
private fun ContentToPreview(
|
||||||
query: String = "",
|
query: String = "",
|
||||||
active: Boolean = false,
|
active: Boolean = false,
|
||||||
|
|
|
||||||
|
|
@ -91,4 +91,7 @@ interface MatrixClient : Closeable {
|
||||||
fun roomMembershipObserver(): RoomMembershipObserver
|
fun roomMembershipObserver(): RoomMembershipObserver
|
||||||
|
|
||||||
fun isMe(userId: UserId?) = userId == sessionId
|
fun isMe(userId: UserId?) = userId == sessionId
|
||||||
|
|
||||||
|
suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit>
|
||||||
|
suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,12 @@ interface MatrixRoom : Closeable {
|
||||||
*/
|
*/
|
||||||
suspend fun updateMembers()
|
suspend fun updateMembers()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the members of the room. Note: generally this should not be used, please use
|
||||||
|
* [membersStateFlow] and [updateMembers] instead.
|
||||||
|
*/
|
||||||
|
suspend fun getMembers(limit: Int = 5): Result<List<RoomMember>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will return an updated member or an error.
|
* Will return an updated member or an error.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.api.room.recent
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
|
import io.element.android.libraries.matrix.api.room.toMatrixUser
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
|
||||||
|
private const val MAX_RECENT_DIRECT_ROOMS_TO_RETURN = 5
|
||||||
|
|
||||||
|
data class RecentDirectRoom(
|
||||||
|
val roomId: RoomId,
|
||||||
|
val matrixUser: MatrixUser,
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun MatrixClient.getRecentDirectRooms(
|
||||||
|
maxNumberOfResults: Int = MAX_RECENT_DIRECT_ROOMS_TO_RETURN,
|
||||||
|
): List<RecentDirectRoom> {
|
||||||
|
val result = mutableListOf<RecentDirectRoom>()
|
||||||
|
val foundUserIds = mutableSetOf<UserId>()
|
||||||
|
getRecentlyVisitedRooms().getOrNull()?.let { roomIds ->
|
||||||
|
roomIds
|
||||||
|
.mapNotNull { roomId -> getRoom(roomId) }
|
||||||
|
.filter { it.isDm && it.isJoined() }
|
||||||
|
.map { room ->
|
||||||
|
val otherUser = room.getMembers().getOrNull()
|
||||||
|
?.firstOrNull { it.userId != sessionId }
|
||||||
|
?.takeIf { foundUserIds.add(it.userId) }
|
||||||
|
?.toMatrixUser()
|
||||||
|
if (otherUser != null) {
|
||||||
|
result.add(
|
||||||
|
RecentDirectRoom(room.roomId, otherUser)
|
||||||
|
)
|
||||||
|
// Return early to avoid useless computation
|
||||||
|
if (result.size >= maxNumberOfResults) {
|
||||||
|
return@map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun MatrixRoom.isJoined(): Boolean {
|
||||||
|
return roomInfoFlow.first().currentUserMembership == CurrentUserMembership.JOINED
|
||||||
|
}
|
||||||
|
|
@ -438,6 +438,18 @@ class RustMatrixClient(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit> = withContext(sessionDispatcher) {
|
||||||
|
runCatching {
|
||||||
|
client.trackRecentlyVisitedRoom(roomId.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>> = withContext(sessionDispatcher) {
|
||||||
|
runCatching {
|
||||||
|
client.getRecentlyVisitedRooms().map(::RoomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun syncService(): SyncService = rustSyncService
|
override fun syncService(): SyncService = rustSyncService
|
||||||
|
|
||||||
override fun sessionVerificationService(): SessionVerificationService = verificationService
|
override fun sessionVerificationService(): SessionVerificationService = verificationService
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,16 @@ class RustMatrixRoom(
|
||||||
roomMemberListFetcher.fetchRoomMembers(source = source)
|
roomMemberListFetcher.fetchRoomMembers(source = source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMembers(limit: Int) = withContext(roomDispatcher) {
|
||||||
|
runCatching {
|
||||||
|
innerRoom.members().use {
|
||||||
|
it.nextChunk(limit.toUInt()).orEmpty().map { roomMember ->
|
||||||
|
RoomMemberMapper.map(roomMember)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getUpdatedMember(userId: UserId): Result<RoomMember> = withContext(roomDispatcher) {
|
override suspend fun getUpdatedMember(userId: UserId): Result<RoomMember> = withContext(roomDispatcher) {
|
||||||
runCatching {
|
runCatching {
|
||||||
RoomMemberMapper.map(innerRoom.member(userId.value))
|
RoomMemberMapper.map(innerRoom.member(userId.value))
|
||||||
|
|
|
||||||
|
|
@ -255,4 +255,16 @@ class FakeMatrixClient(
|
||||||
fun givenRemoveAvatarResult(result: Result<Unit>) {
|
fun givenRemoveAvatarResult(result: Result<Unit>) {
|
||||||
removeAvatarResult = result
|
removeAvatarResult = result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val visitedRoomsId: MutableList<RoomId> = mutableListOf()
|
||||||
|
|
||||||
|
override suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit> {
|
||||||
|
visitedRoomsId.removeAll { it == roomId }
|
||||||
|
visitedRoomsId.add(0, roomId)
|
||||||
|
return Result.success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>> {
|
||||||
|
return Result.success(visitedRoomsId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,10 @@ class FakeMatrixRoom(
|
||||||
return getRoomMemberResult
|
return getRoomMemberResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMembers(limit: Int): Result<List<RoomMember>> {
|
||||||
|
return Result.success(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun updateRoomNotificationSettings(): Result<Unit> = simulateLongTask {
|
override suspend fun updateRoomNotificationSettings(): Result<Unit> = simulateLongTask {
|
||||||
val notificationSettings = notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow()
|
val notificationSettings = notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow()
|
||||||
roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Ready(notificationSettings)
|
roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Ready(notificationSettings)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:9828a78e660d9dfa53fcdcb5febfd45e2f112404e24ba8126e6a910254a064fc
|
||||||
|
size 45359
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:8a7a6d9290e356382d127fa54d445a25fb5baea30689ded2999dfe199f593bb6
|
||||||
|
size 43446
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:cb2fe3d47c7af259c25e0d640d60cdbd904b0c99a6e75ff773136d55b3986db9
|
||||||
|
size 40818
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:0b7a41ea00505a0b6263ce4e6f906be24df830e83864a81b54baafa3a0434666
|
||||||
|
size 39453
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:0b7aa4ae1aca7c541a0c8a7bc535f7ff98dd3b24127d0dcd9e76ef2a889e6ab4
|
||||||
|
size 52249
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:780070d15ef80d123a41f071dd5d13404cf65950944a94be364539607c3cba2a
|
||||||
|
size 49687
|
||||||
Loading…
Add table
Add a link
Reference in a new issue