Browse Source

Handle errors on create DM

test/jme/compound-poc
Florian Renaud 2 years ago
parent
commit
36afd71c29
  1. 2
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt
  2. 22
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt
  3. 13
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt
  4. 17
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt
  5. 72
      features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt
  6. 11
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialog.kt
  7. 9
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt

2
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt

@ -21,4 +21,6 @@ import io.element.android.libraries.matrix.ui.model.MatrixUser @@ -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
}

22
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt

@ -50,16 +50,24 @@ class CreateRoomRootPresenter @Inject constructor( @@ -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
}
}

13
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt

@ -36,7 +36,18 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot @@ -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,
)
}
),
)
}

17
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt

@ -42,6 +42,7 @@ import io.element.android.features.createroom.impl.R @@ -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( @@ -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
}
}

72
features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt

@ -18,18 +18,23 @@ @@ -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 @@ -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 { @@ -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 { @@ -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
}
}
}

11
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialog.kt

@ -37,7 +37,9 @@ fun ErrorDialog( @@ -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( @@ -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,

9
libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt

@ -39,6 +39,7 @@ class FakeMatrixClient( @@ -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( @@ -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( @@ -91,7 +94,7 @@ class FakeMatrixClient(
// Mocks
fun givenLogoutError(failure: Throwable) {
fun givenLogoutError(failure: Throwable?) {
logoutFailure = failure
}
@ -99,6 +102,10 @@ class FakeMatrixClient( @@ -99,6 +102,10 @@ class FakeMatrixClient(
createDmResult = result
}
fun givenCreateDmError(failure: Throwable?) {
createDmFailure = failure
}
fun givenFindDmResult(result: MatrixRoom?) {
findDmResult = result
}

Loading…
Cancel
Save