Avoid using runBlocking in Node resolve function.
This commit is contained in:
@@ -26,16 +26,13 @@ import io.element.android.libraries.architecture.BackstackView
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
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 kotlinx.coroutines.runBlocking
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class CreateRoomFlowNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val client: MatrixClient,
|
||||
) : BaseFlowNode<CreateRoomFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.ConfigureRoom,
|
||||
@@ -59,8 +56,7 @@ class CreateRoomFlowNode @AssistedInject constructor(
|
||||
createNode<ConfigureRoomNode>(buildContext, plugins = listOf(callback))
|
||||
}
|
||||
is NavTarget.AddPeople -> {
|
||||
val joinedRoom = runBlocking { client.getJoinedRoom(navTarget.roomId) } ?: error("Room not found")
|
||||
val inputs = AddPeopleNode.Inputs(joinedRoom)
|
||||
val inputs = AddPeopleNode.Inputs(navTarget.roomId)
|
||||
val callback: AddPeopleNode.Callback = object : AddPeopleNode.Callback {
|
||||
override fun onFinish() {
|
||||
onRoomCreated(navTarget.roomId)
|
||||
|
||||
@@ -21,7 +21,7 @@ import io.element.android.features.invitepeople.api.InvitePeopleRenderer
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class AddPeopleNode @AssistedInject constructor(
|
||||
@@ -31,7 +31,7 @@ class AddPeopleNode @AssistedInject constructor(
|
||||
private val invitePeopleRenderer: InvitePeopleRenderer,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
data class Inputs(
|
||||
val joinedRoom: JoinedRoom
|
||||
val roomId: RoomId,
|
||||
) : NodeInputs
|
||||
|
||||
interface Callback : Plugin {
|
||||
@@ -42,8 +42,11 @@ class AddPeopleNode @AssistedInject constructor(
|
||||
plugins<Callback>().forEach { it.onFinish() }
|
||||
}
|
||||
|
||||
private val joinedRoom = inputs<Inputs>().joinedRoom
|
||||
private val invitePeoplePresenter = invitePeoplePresenterFactory.create(joinedRoom)
|
||||
private val roomId = inputs<Inputs>().roomId
|
||||
private val invitePeoplePresenter = invitePeoplePresenterFactory.create(
|
||||
joinedRoom = null,
|
||||
roomId = roomId,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
package io.element.android.features.invitepeople.api
|
||||
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
|
||||
interface InvitePeoplePresenter : Presenter<InvitePeopleState> {
|
||||
interface Factory {
|
||||
fun create(room: JoinedRoom): InvitePeoplePresenter
|
||||
fun create(
|
||||
joinedRoom: JoinedRoom?,
|
||||
roomId: RoomId,
|
||||
): InvitePeoplePresenter
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -23,11 +24,14 @@ import io.element.android.features.invitepeople.api.InvitePeopleEvents
|
||||
import io.element.android.features.invitepeople.api.InvitePeoplePresenter
|
||||
import io.element.android.features.invitepeople.api.InvitePeopleState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.map
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
@@ -46,16 +50,18 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class DefaultInvitePeoplePresenter @AssistedInject constructor(
|
||||
@Assisted private val room: JoinedRoom,
|
||||
@Assisted private val joinedRoom: JoinedRoom?,
|
||||
@Assisted private val roomId: RoomId,
|
||||
private val userRepository: UserRepository,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
|
||||
private val appErrorStateService: AppErrorStateService,
|
||||
private val matrixClient: MatrixClient,
|
||||
) : InvitePeoplePresenter {
|
||||
@AssistedFactory
|
||||
@ContributesBinding(SessionScope::class)
|
||||
interface Factory : InvitePeoplePresenter.Factory {
|
||||
override fun create(room: JoinedRoom): DefaultInvitePeoplePresenter
|
||||
override fun create(joinedRoom: JoinedRoom?, roomId: RoomId): DefaultInvitePeoplePresenter
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -66,9 +72,21 @@ class DefaultInvitePeoplePresenter @AssistedInject constructor(
|
||||
var searchQuery by rememberSaveable { mutableStateOf("") }
|
||||
var searchActive by rememberSaveable { mutableStateOf(false) }
|
||||
val showSearchLoader = rememberSaveable { mutableStateOf(false) }
|
||||
val room by produceState(if (joinedRoom != null) AsyncData.Success(joinedRoom) else AsyncData.Loading()) {
|
||||
if (joinedRoom == null) {
|
||||
val result = matrixClient.getJoinedRoom(roomId)
|
||||
value = if (result == null) {
|
||||
AsyncData.Failure(Exception("Room not found"))
|
||||
} else {
|
||||
AsyncData.Success(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
fetchMembers(roomMembers)
|
||||
LaunchedEffect(room.isSuccess()) {
|
||||
room.dataOrNull()?.let {
|
||||
fetchMembers(it, roomMembers)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(searchQuery, roomMembers) {
|
||||
performSearch(
|
||||
@@ -96,7 +114,9 @@ class DefaultInvitePeoplePresenter @AssistedInject constructor(
|
||||
searchResults.toggleUser(event.user)
|
||||
}
|
||||
is InvitePeopleEvents.SendInvites -> {
|
||||
sessionCoroutineScope.sendInvites(selectedUsers.value)
|
||||
room.dataOrNull()?.let {
|
||||
sessionCoroutineScope.sendInvites(it, selectedUsers.value)
|
||||
}
|
||||
}
|
||||
is InvitePeopleEvents.CloseSearch -> {
|
||||
searchActive = false
|
||||
@@ -106,6 +126,7 @@ class DefaultInvitePeoplePresenter @AssistedInject constructor(
|
||||
}
|
||||
|
||||
return DefaultInvitePeopleState(
|
||||
room = room.map { },
|
||||
canInvite = selectedUsers.value.isNotEmpty(),
|
||||
selectedUsers = selectedUsers.value,
|
||||
searchQuery = searchQuery,
|
||||
@@ -116,7 +137,10 @@ class DefaultInvitePeoplePresenter @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.sendInvites(selectedUsers: List<MatrixUser>) = launch {
|
||||
private fun CoroutineScope.sendInvites(
|
||||
room: JoinedRoom,
|
||||
selectedUsers: List<MatrixUser>,
|
||||
) = launch {
|
||||
val anyInviteFailed = selectedUsers
|
||||
.map { room.inviteUserById(it.userId) }
|
||||
.any { it.isFailure }
|
||||
@@ -186,7 +210,10 @@ class DefaultInvitePeoplePresenter @AssistedInject constructor(
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
private suspend fun fetchMembers(roomMembers: MutableState<AsyncData<ImmutableList<RoomMember>>>) {
|
||||
private suspend fun fetchMembers(
|
||||
room: JoinedRoom,
|
||||
roomMembers: MutableState<AsyncData<ImmutableList<RoomMember>>>
|
||||
) {
|
||||
suspend {
|
||||
room.filterMembers("", coroutineDispatchers.io).toImmutableList()
|
||||
}.runCatchingUpdatingState(roomMembers)
|
||||
|
||||
@@ -9,11 +9,13 @@ package io.element.android.features.invitepeople.impl
|
||||
|
||||
import io.element.android.features.invitepeople.api.InvitePeopleEvents
|
||||
import io.element.android.features.invitepeople.api.InvitePeopleState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class DefaultInvitePeopleState(
|
||||
val room: AsyncData<Unit>,
|
||||
override val canInvite: Boolean,
|
||||
val searchQuery: String,
|
||||
val showSearchLoader: Boolean,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
package io.element.android.features.invitepeople.impl
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
@@ -66,6 +67,7 @@ internal class DefaultInvitePeopleStateProvider : PreviewParameterProvider<Defau
|
||||
),
|
||||
showSearchLoader = true,
|
||||
),
|
||||
aDefaultInvitePeopleState(room = AsyncData.Failure(Exception("Room not found"))),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -84,6 +86,7 @@ private fun anInvitableUser(
|
||||
)
|
||||
|
||||
private fun aDefaultInvitePeopleState(
|
||||
room: AsyncData<Unit> = AsyncData.Success(Unit),
|
||||
canInvite: Boolean = false,
|
||||
searchQuery: String = "",
|
||||
searchResults: SearchBarResultState<ImmutableList<InvitableUser>> = SearchBarResultState.Initial(),
|
||||
@@ -92,6 +95,7 @@ private fun aDefaultInvitePeopleState(
|
||||
showSearchLoader: Boolean = false,
|
||||
): DefaultInvitePeopleState {
|
||||
return DefaultInvitePeopleState(
|
||||
room = room,
|
||||
canInvite = canInvite,
|
||||
searchQuery = searchQuery,
|
||||
searchResults = searchResults,
|
||||
|
||||
@@ -8,19 +8,24 @@
|
||||
package io.element.android.features.invitepeople.impl
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncFailure
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncLoading
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
@@ -42,9 +47,39 @@ import kotlinx.collections.immutable.ImmutableList
|
||||
fun InvitePeopleView(
|
||||
state: DefaultInvitePeopleState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (state.room) {
|
||||
is AsyncData.Failure -> InvitePeopleViewError(state.room.error, modifier)
|
||||
AsyncData.Uninitialized,
|
||||
is AsyncData.Loading,
|
||||
is AsyncData.Success -> InvitePeopleContentView(state, modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InvitePeopleViewError(
|
||||
error: Throwable,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
AsyncFailure(
|
||||
throwable = error,
|
||||
onRetry = null,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InvitePeopleContentView(
|
||||
state: DefaultInvitePeopleState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
modifier = modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
InvitePeopleSearchBar(
|
||||
|
||||
@@ -10,15 +10,21 @@ package io.element.android.features.invitepeople.impl
|
||||
import app.cash.turbine.ReceiveTurbine
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.invitepeople.api.InvitePeopleEvents
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
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.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.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMemberList
|
||||
@@ -54,7 +60,7 @@ internal class DefaultInvitePeoplePresenterTest {
|
||||
val presenter = createDefaultInvitePeoplePresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItemAsDefault()
|
||||
|
||||
assertThat(initialState.room).isEqualTo(AsyncData.Success(Unit))
|
||||
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
|
||||
assertThat(initialState.isSearchActive).isFalse()
|
||||
assertThat(initialState.canInvite).isFalse()
|
||||
@@ -452,6 +458,40 @@ internal class DefaultInvitePeoplePresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when joinedRoom is not provided, it is retrieved on the MatrixClient`() = runTest {
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, FakeJoinedRoom())
|
||||
}
|
||||
val presenter = createDefaultInvitePeoplePresenter(
|
||||
joinedRoom = null,
|
||||
roomId = A_ROOM_ID,
|
||||
matrixClient = matrixClient,
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItemAsDefault()
|
||||
assertThat(initialState.room.isLoading()).isTrue()
|
||||
val finalState = awaitItemAsDefault()
|
||||
assertThat(finalState.room).isEqualTo(AsyncData.Success(Unit))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when joinedRoom is not provided, it is retrieved on the MatrixClient - error case`() = runTest {
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val presenter = createDefaultInvitePeoplePresenter(
|
||||
joinedRoom = null,
|
||||
roomId = A_ROOM_ID,
|
||||
matrixClient = matrixClient,
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItemAsDefault()
|
||||
assertThat(initialState.room.isLoading()).isTrue()
|
||||
val finalState = awaitItemAsDefault()
|
||||
assertThat(finalState.room.errorOrNull()?.message).isEqualTo("Room not found")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun FakeUserRepository.emitStateWithUsers(
|
||||
users: List<MatrixUser>,
|
||||
isSearching: Boolean = false
|
||||
@@ -484,19 +524,24 @@ private suspend fun <T> ReceiveTurbine<T>.awaitItemAsDefault(): DefaultInvitePeo
|
||||
fun TestScope.createDefaultInvitePeoplePresenter(
|
||||
roomMembersState: RoomMembersState = RoomMembersState.Ready(aRoomMemberList()),
|
||||
inviteUserResult: (UserId) -> Result<Unit> = { lambdaError() },
|
||||
joinedRoom: JoinedRoom? = FakeJoinedRoom(
|
||||
inviteUserResult = inviteUserResult,
|
||||
).apply {
|
||||
givenRoomMembersState(roomMembersState)
|
||||
},
|
||||
roomId: RoomId = A_ROOM_ID,
|
||||
userRepository: UserRepository = FakeUserRepository(),
|
||||
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||
appErrorStateService: AppErrorStateService = FakeAppErrorStateService(),
|
||||
matrixClient: MatrixClient = FakeMatrixClient(),
|
||||
): DefaultInvitePeoplePresenter {
|
||||
return DefaultInvitePeoplePresenter(
|
||||
room = FakeJoinedRoom(
|
||||
inviteUserResult = inviteUserResult,
|
||||
).apply {
|
||||
givenRoomMembersState(roomMembersState)
|
||||
},
|
||||
joinedRoom = joinedRoom,
|
||||
roomId = roomId,
|
||||
userRepository = userRepository,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
coroutineScope = backgroundScope,
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
appErrorStateService = appErrorStateService,
|
||||
matrixClient = matrixClient,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,10 @@ class RoomInviteMembersNode @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private val invitePeoplePresenter = invitePeoplePresenterFactory.create(room)
|
||||
private val invitePeoplePresenter = invitePeoplePresenterFactory.create(
|
||||
joinedRoom = room,
|
||||
roomId = room.roomId,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
|
||||
@@ -160,3 +160,17 @@ suspend inline fun <T> runUpdatingState(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
inline fun <T, R> AsyncData<T>.map(
|
||||
transform: (T?) -> R,
|
||||
): AsyncData<R> {
|
||||
return when (this) {
|
||||
is AsyncData.Failure -> AsyncData.Failure(
|
||||
error = error,
|
||||
prevData = transform(prevData)
|
||||
)
|
||||
is AsyncData.Loading -> AsyncData.Loading(transform(prevData))
|
||||
is AsyncData.Success -> AsyncData.Success(transform(data))
|
||||
AsyncData.Uninitialized -> AsyncData.Uninitialized
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user