diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt index d3cd1e0287..de9810f34a 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt +++ b/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 sealed interface CreateRoomRootEvents { object InvitePeople : CreateRoomRootEvents data class StartDM(val matrixUser: MatrixUser) : CreateRoomRootEvents + object RetryStartDM : CreateRoomRootEvents + object CancelStartDM : CreateRoomRootEvents } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt index 2ec32c7bac..6bc8d3c45e 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt @@ -50,16 +50,24 @@ class CreateRoomRootPresenter @Inject constructor( val localCoroutineScope = rememberCoroutineScope() val startDmAction: MutableState> = 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 } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt index e0e3152318..e8f739c738 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt @@ -36,7 +36,18 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider { + 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 } } diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt index c3cacb25c7..6853fed3d8 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt @@ -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 + } + } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialog.kt index ce1730ed0c..da1aeb836e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialog.kt @@ -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, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index ec5068e93d..5889d35e0c 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -39,6 +39,7 @@ class FakeMatrixClient( ) : MatrixClient { private var createDmResult: Result = 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 { + 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 }