Handle errors on create DM
This commit is contained in:
@@ -21,4 +21,6 @@ import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||
sealed interface CreateRoomRootEvents {
|
||||
object InvitePeople : CreateRoomRootEvents
|
||||
data class StartDM(val matrixUser: MatrixUser) : CreateRoomRootEvents
|
||||
object RetryStartDM : CreateRoomRootEvents
|
||||
object CancelStartDM : CreateRoomRootEvents
|
||||
}
|
||||
|
||||
@@ -50,16 +50,24 @@ class CreateRoomRootPresenter @Inject constructor(
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val startDmAction: MutableState<Async<RoomId>> = remember { mutableStateOf(Async.Uninitialized) }
|
||||
|
||||
fun startDm(matrixUser: MatrixUser) {
|
||||
startDmAction.value = Async.Uninitialized
|
||||
val existingDM = matrixClient.findDM(matrixUser.id)
|
||||
if (existingDM == null) {
|
||||
localCoroutineScope.createDM(matrixUser, startDmAction)
|
||||
} else {
|
||||
startDmAction.value = Async.Success(existingDM.roomId)
|
||||
}
|
||||
}
|
||||
|
||||
fun handleEvents(event: CreateRoomRootEvents) {
|
||||
when (event) {
|
||||
is CreateRoomRootEvents.StartDM -> {
|
||||
val existingDM = matrixClient.findDM(event.matrixUser.id)
|
||||
if (existingDM == null) {
|
||||
localCoroutineScope.createDM(event.matrixUser, startDmAction)
|
||||
} else {
|
||||
startDmAction.value = Async.Success(existingDM.roomId)
|
||||
}
|
||||
is CreateRoomRootEvents.StartDM -> startDm(event.matrixUser)
|
||||
CreateRoomRootEvents.RetryStartDM -> {
|
||||
startDmAction.value = Async.Uninitialized
|
||||
selectUsersState.selectedUsers.firstOrNull()?.let { startDm(it) }
|
||||
}
|
||||
CreateRoomRootEvents.CancelStartDM -> startDmAction.value = Async.Uninitialized
|
||||
CreateRoomRootEvents.InvitePeople -> Unit // Todo Handle invite people action
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,18 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
|
||||
isSearchActive = true,
|
||||
)
|
||||
}
|
||||
)
|
||||
),
|
||||
aCreateRoomRootState().copy(
|
||||
startDmAction = Async.Failure(Throwable("An error occurred when trying to start a chat")),
|
||||
selectUsersState = aMatrixUser().let {
|
||||
aSelectUsersState().copy(
|
||||
searchQuery = it.id.value,
|
||||
searchResults = persistentListOf(it),
|
||||
selectedUsers = persistentListOf(it),
|
||||
isSearchActive = true,
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import io.element.android.features.createroom.impl.R
|
||||
import io.element.android.features.selectusers.api.SelectUsersView
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.theme.components.CenterAlignedTopAppBar
|
||||
@@ -95,8 +96,20 @@ fun CreateRoomRootView(
|
||||
}
|
||||
}
|
||||
|
||||
if (state.startDmAction is Async.Loading) {
|
||||
ProgressDialog(text = stringResource(id = StringR.string.common_creating_room))
|
||||
when (state.startDmAction) {
|
||||
is Async.Loading -> {
|
||||
ProgressDialog(text = stringResource(id = StringR.string.common_creating_room))
|
||||
}
|
||||
is Async.Failure -> {
|
||||
ErrorDialog(
|
||||
content = stringResource(id = StringR.string.screen_start_chat_error_starting_chat),
|
||||
dismissText = stringResource(id = StringR.string.action_cancel),
|
||||
submitText = stringResource(id = StringR.string.action_retry),
|
||||
onDismiss = { state.eventSink(CreateRoomRootEvents.CancelStartDM) },
|
||||
onSubmit = { state.eventSink(CreateRoomRootEvents.RetryStartDM) },
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,18 +18,23 @@
|
||||
|
||||
package io.element.android.features.createroom.impl.root
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import app.cash.molecule.RecompositionClock
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.selectusers.api.SelectUsersPresenter
|
||||
import io.element.android.features.selectusers.api.SelectUsersPresenterArgs
|
||||
import io.element.android.features.selectusers.impl.DefaultSelectUsersPresenter
|
||||
import io.element.android.features.selectusers.api.SelectUsersState
|
||||
import io.element.android.features.selectusers.api.aSelectUsersState
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
@@ -38,15 +43,17 @@ import org.junit.Test
|
||||
class CreateRoomRootPresenterTests {
|
||||
|
||||
private lateinit var presenter: CreateRoomRootPresenter
|
||||
private lateinit var fakeSelectUsersPresenter: FakeSelectUserPresenter
|
||||
private lateinit var fakeMatrixClient: FakeMatrixClient
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val selectUsersPresenter = object : DefaultSelectUsersPresenter.DefaultSelectUsersFactory {
|
||||
override fun create(args: SelectUsersPresenterArgs) = DefaultSelectUsersPresenter(args)
|
||||
val factory = object : SelectUsersPresenter.Factory {
|
||||
override fun create(args: SelectUsersPresenterArgs) = fakeSelectUsersPresenter
|
||||
}
|
||||
fakeSelectUsersPresenter = FakeSelectUserPresenter()
|
||||
fakeMatrixClient = FakeMatrixClient()
|
||||
presenter = CreateRoomRootPresenter(selectUsersPresenter, fakeMatrixClient)
|
||||
presenter = CreateRoomRootPresenter(factory, fakeMatrixClient)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -82,6 +89,7 @@ class CreateRoomRootPresenterTests {
|
||||
fakeMatrixClient.givenCreateDmResult(createDmResult)
|
||||
|
||||
initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
||||
assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java)
|
||||
val stateAfterStartDM = awaitItem()
|
||||
assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Success::class.java)
|
||||
assertThat(stateAfterStartDM.startDmAction.dataOrNull()).isEqualTo(createDmResult.getOrNull())
|
||||
@@ -105,4 +113,60 @@ class CreateRoomRootPresenterTests {
|
||||
assertThat(stateAfterStartDM.startDmAction.dataOrNull()).isEqualTo(fakeDmResult.roomId)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - trigger retry create DM action`() = runTest {
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
val matrixUser = MatrixUser(UserId("@name:matrix.org"))
|
||||
val createDmResult = Result.success(RoomId("!createDmResult"))
|
||||
fakeSelectUsersPresenter.givenState(aSelectUsersState().copy(selectedUsers = persistentListOf(matrixUser)))
|
||||
|
||||
fakeMatrixClient.givenFindDmResult(null)
|
||||
fakeMatrixClient.givenCreateDmError(A_THROWABLE)
|
||||
fakeMatrixClient.givenCreateDmResult(createDmResult)
|
||||
|
||||
// Failure
|
||||
initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
||||
assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java)
|
||||
val stateAfterStartDM = awaitItem()
|
||||
assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Failure::class.java)
|
||||
|
||||
// Cancel
|
||||
stateAfterStartDM.eventSink(CreateRoomRootEvents.CancelStartDM)
|
||||
val stateAfterCancel = awaitItem()
|
||||
assertThat(stateAfterCancel.startDmAction).isInstanceOf(Async.Uninitialized::class.java)
|
||||
|
||||
// Failure
|
||||
stateAfterCancel.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
||||
assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java)
|
||||
val stateAfterSecondAttempt = awaitItem()
|
||||
assertThat(stateAfterSecondAttempt.startDmAction).isInstanceOf(Async.Failure::class.java)
|
||||
|
||||
// Retry with success
|
||||
fakeMatrixClient.givenCreateDmError(null)
|
||||
stateAfterSecondAttempt.eventSink(CreateRoomRootEvents.RetryStartDM)
|
||||
assertThat(awaitItem().startDmAction).isInstanceOf(Async.Uninitialized::class.java)
|
||||
assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java)
|
||||
val stateAfterRetryStartDM = awaitItem()
|
||||
assertThat(stateAfterRetryStartDM.startDmAction).isInstanceOf(Async.Success::class.java)
|
||||
assertThat(stateAfterRetryStartDM.startDmAction.dataOrNull()).isEqualTo(createDmResult.getOrNull())
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeSelectUserPresenter : SelectUsersPresenter {
|
||||
|
||||
private var state = aSelectUsersState()
|
||||
|
||||
fun givenState(state: SelectUsersState) {
|
||||
this.state = state
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): SelectUsersState {
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,9 @@ fun ErrorDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String = ErrorDialogDefaults.title,
|
||||
submitText: String = ErrorDialogDefaults.submitText,
|
||||
dismissText: String? = null,
|
||||
onDismiss: () -> Unit = {},
|
||||
onSubmit: () -> Unit = onDismiss,
|
||||
shape: Shape = AlertDialogDefaults.shape,
|
||||
containerColor: Color = AlertDialogDefaults.containerColor,
|
||||
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
|
||||
@@ -55,10 +57,17 @@ fun ErrorDialog(
|
||||
Text(content)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
TextButton(onClick = onSubmit) {
|
||||
Text(submitText)
|
||||
}
|
||||
},
|
||||
dismissButton = dismissText?.let {
|
||||
{
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(it)
|
||||
}
|
||||
}
|
||||
},
|
||||
shape = shape,
|
||||
containerColor = containerColor,
|
||||
iconContentColor = iconContentColor,
|
||||
|
||||
@@ -39,6 +39,7 @@ class FakeMatrixClient(
|
||||
) : MatrixClient {
|
||||
|
||||
private var createDmResult: Result<RoomId> = Result.success(A_ROOM_ID)
|
||||
private var createDmFailure: Throwable? = null
|
||||
private var findDmResult: MatrixRoom? = FakeMatrixRoom()
|
||||
private var logoutFailure: Throwable? = null
|
||||
|
||||
@@ -47,6 +48,8 @@ class FakeMatrixClient(
|
||||
}
|
||||
|
||||
override suspend fun createDM(userId: UserId): Result<RoomId> {
|
||||
delay(100)
|
||||
createDmFailure?.let { throw it }
|
||||
return createDmResult
|
||||
}
|
||||
|
||||
@@ -91,7 +94,7 @@ class FakeMatrixClient(
|
||||
|
||||
// Mocks
|
||||
|
||||
fun givenLogoutError(failure: Throwable) {
|
||||
fun givenLogoutError(failure: Throwable?) {
|
||||
logoutFailure = failure
|
||||
}
|
||||
|
||||
@@ -99,6 +102,10 @@ class FakeMatrixClient(
|
||||
createDmResult = result
|
||||
}
|
||||
|
||||
fun givenCreateDmError(failure: Throwable?) {
|
||||
createDmFailure = failure
|
||||
}
|
||||
|
||||
fun givenFindDmResult(result: MatrixRoom?) {
|
||||
findDmResult = result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user