From 81fc3406c8dbcfe6bb0435d8328336d78f5deb48 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Apr 2023 00:20:33 +0200 Subject: [PATCH 1/6] Handle create room action --- .../createroom/impl/ConfigureRoomFlowNode.kt | 4 +- .../createroom/impl/CreateRoomFlowNode.kt | 8 ++- .../impl/configureroom/ConfigureRoomEvents.kt | 4 +- .../impl/configureroom/ConfigureRoomNode.kt | 15 +++- .../configureroom/ConfigureRoomPresenter.kt | 43 ++++++++++- .../impl/configureroom/ConfigureRoomState.kt | 3 + .../ConfigureRoomStateProvider.kt | 2 + .../impl/configureroom/ConfigureRoomView.kt | 29 +++++++- .../impl/src/main/res/values/localazy.xml | 1 + .../ConfigureRoomPresenterTests.kt | 6 +- .../libraries/matrix/impl/RustMatrixClient.kt | 72 ++++++++++--------- .../src/main/res/values/localazy.xml | 45 ------------ 12 files changed, 144 insertions(+), 88 deletions(-) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt index a01f0747f5..2575c8b66f 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/ConfigureRoomFlowNode.kt @@ -76,10 +76,10 @@ class ConfigureRoomFlowNode @AssistedInject constructor( backstack.push(NavTarget.ConfigureRoom) } } - createNode(context = buildContext, plugins = listOf(callback)) + createNode(context = buildContext, plugins = plugins.plus(callback)) } NavTarget.ConfigureRoom -> { - createNode(context = buildContext) + createNode(context = buildContext, plugins = plugins) } } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt index 6b9edc68d2..1376a7270f 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt @@ -30,6 +30,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.createroom.api.CreateRoomEntryPoint +import io.element.android.features.createroom.impl.configureroom.ConfigureRoomNode import io.element.android.features.createroom.impl.root.CreateRoomRootNode import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler @@ -74,7 +75,12 @@ class CreateRoomFlowNode @AssistedInject constructor( createNode(context = buildContext, plugins = listOf(callback)) } NavTarget.NewRoom -> { - createNode(context = buildContext, plugins = emptyList()) + val callback = object : ConfigureRoomNode.Callback { + override fun onRoomCreated(roomId: RoomId) { + plugins().forEach { it.onOpenRoom(roomId) } + } + } + createNode(context = buildContext, plugins = listOf(callback)) } } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt index f10f673d78..c444e83c16 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt @@ -17,6 +17,7 @@ package io.element.android.features.createroom.impl.configureroom import android.net.Uri +import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.libraries.matrix.ui.model.MatrixUser sealed interface ConfigureRoomEvents { @@ -25,5 +26,6 @@ sealed interface ConfigureRoomEvents { data class AvatarUriChanged(val uri: Uri?) : ConfigureRoomEvents data class RoomPrivacyChanged(val privacy: RoomPrivacy?) : ConfigureRoomEvents data class RemoveFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents - object CreateRoom : ConfigureRoomEvents + data class CreateRoom(val config: CreateRoomConfig) : ConfigureRoomEvents + object CancelCreateRoom : ConfigureRoomEvents } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt index 08a4f5f64b..aaa6f9667a 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt @@ -21,10 +21,12 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.createroom.impl.di.CreateRoomScope +import io.element.android.libraries.matrix.api.core.RoomId @ContributesNode(CreateRoomScope::class) class ConfigureRoomNode @AssistedInject constructor( @@ -33,13 +35,24 @@ class ConfigureRoomNode @AssistedInject constructor( private val presenter: ConfigureRoomPresenter, ) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun onRoomCreated(roomId: RoomId) + } + + private val callback = object : Callback { + override fun onRoomCreated(roomId: RoomId) { + plugins().forEach { it.onRoomCreated(roomId) } + } + } + @Composable override fun View(modifier: Modifier) { val state = presenter.present() ConfigureRoomView( state = state, modifier = modifier, - onBackPressed = { navigateUp() } // TODO we should keep in memory the current view state + onBackPressed = this::navigateUp, + onRoomCreated = callback::onRoomCreated ) } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt index 0046b83058..99d093a4ee 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt @@ -17,17 +17,30 @@ package io.element.android.features.createroom.impl.configureroom import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.CreateRoomDataStore +import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.execute +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters +import io.element.android.libraries.matrix.api.createroom.RoomPreset +import io.element.android.libraries.matrix.api.createroom.RoomVisibility +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import javax.inject.Inject class ConfigureRoomPresenter @Inject constructor( private val dataStore: CreateRoomDataStore, + private val matrixClient: MatrixClient, ) : Presenter { @Composable @@ -39,6 +52,14 @@ class ConfigureRoomPresenter @Inject constructor( } } + val localCoroutineScope = rememberCoroutineScope() + val createRoomAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + + fun createRoom(config: CreateRoomConfig) { + createRoomAction.value = Async.Uninitialized + localCoroutineScope.createRoom(config, createRoomAction) + } + fun handleEvents(event: ConfigureRoomEvents) { when (event) { is ConfigureRoomEvents.AvatarUriChanged -> dataStore.setAvatarUrl(event.uri?.toString()) @@ -46,14 +67,32 @@ class ConfigureRoomPresenter @Inject constructor( is ConfigureRoomEvents.TopicChanged -> dataStore.setTopic(event.topic) is ConfigureRoomEvents.RoomPrivacyChanged -> dataStore.setPrivacy(event.privacy) is ConfigureRoomEvents.RemoveFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser) - ConfigureRoomEvents.CreateRoom -> Unit // TODO + is ConfigureRoomEvents.CreateRoom -> createRoom(event.config) + ConfigureRoomEvents.CancelCreateRoom -> createRoomAction.value = Async.Uninitialized } } return ConfigureRoomState( - createRoomConfig.value, + config = createRoomConfig.value, isCreateButtonEnabled = isCreateButtonEnabled, + createRoomAction = createRoomAction.value, eventSink = ::handleEvents, ) } + + private fun CoroutineScope.createRoom(config: CreateRoomConfig, createRoomAction: MutableState>) = launch { + suspend { + val params = CreateRoomParameters( + name = config.roomName, + topic = config.topic, + isEncrypted = config.privacy == RoomPrivacy.Private, + isDirect = false, + visibility = if (config.privacy == RoomPrivacy.Public) RoomVisibility.PUBLIC else RoomVisibility.PRIVATE, + preset = if (config.privacy == RoomPrivacy.Public) RoomPreset.PUBLIC_CHAT else RoomPreset.PRIVATE_CHAT, + invite = config.invites.map { it.id }, + avatar = config.avatarUrl, + ) + matrixClient.createRoom(params).getOrThrow() + }.execute(createRoomAction) + } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt index 47be0b1f17..b5f804fcde 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt @@ -17,9 +17,12 @@ package io.element.android.features.createroom.impl.configureroom import io.element.android.features.createroom.impl.CreateRoomConfig +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.core.RoomId data class ConfigureRoomState( val config: CreateRoomConfig, val isCreateButtonEnabled: Boolean, + val createRoomAction: Async, val eventSink: (ConfigureRoomEvents) -> Unit ) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt index 32744178dd..67e0b91bba 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt @@ -19,6 +19,7 @@ package io.element.android.features.createroom.impl.configureroom import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.userlist.api.aListOfSelectedUsers +import io.element.android.libraries.architecture.Async open class ConfigureRoomStateProvider : PreviewParameterProvider { override val values: Sequence @@ -39,5 +40,6 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider Unit = {}, + onRoomCreated: (RoomId) -> Unit = {}, ) { + if (state.createRoomAction is Async.Success) { + LaunchedEffect(state.createRoomAction) { + onRoomCreated(state.createRoomAction.state) + } + } + val context = LocalContext.current Scaffold( modifier = modifier, @@ -67,8 +79,7 @@ fun ConfigureRoomView( isNextActionEnabled = state.isCreateButtonEnabled, onBackPressed = onBackPressed, onNextPressed = { - // state.eventSink(ConfigureRoomEvents.CreateRoom) - Toast.makeText(context, "not implemented yet", Toast.LENGTH_SHORT).show() + state.eventSink(ConfigureRoomEvents.CreateRoom(state.config)) }, ) } @@ -102,6 +113,20 @@ fun ConfigureRoomView( ) } } + + when (state.createRoomAction) { + is Async.Loading -> { + ProgressDialog(text = stringResource(StringR.string.common_creating_room)) + } + is Async.Failure -> { + RetryDialog( + content = stringResource(R.string.screen_create_room_error_creating_room), + onDismiss = { state.eventSink(ConfigureRoomEvents.CancelCreateRoom) }, + onRetry = { state.eventSink(ConfigureRoomEvents.CreateRoom(state.config)) }, + ) + } + else -> Unit + } } @Composable diff --git a/features/createroom/impl/src/main/res/values/localazy.xml b/features/createroom/impl/src/main/res/values/localazy.xml index 177d588f7e..5d78791ee4 100644 --- a/features/createroom/impl/src/main/res/values/localazy.xml +++ b/features/createroom/impl/src/main/res/values/localazy.xml @@ -3,6 +3,7 @@ "New room" "Invite people" "Add people" + "An error occurred when creating the room" "Messages in this room are encrypted. Encryption can’t be disabled afterwards." "Private room (invite only)" "Messages are not encrypted and anyone can read them. You can enable encryption at a later date." diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt index 1f27baa511..01e5b175da 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt @@ -29,6 +29,7 @@ import io.element.android.features.userlist.api.UserListDataStore import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_ROOM_NAME +import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.ui.components.aMatrixUser import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -48,7 +49,10 @@ class ConfigureRoomPresenterTests { @Before fun setup() { userListDataStore = UserListDataStore() - presenter = ConfigureRoomPresenter(CreateRoomDataStore(userListDataStore)) + presenter = ConfigureRoomPresenter( + dataStore = CreateRoomDataStore(userListDataStore), + matrixClient = FakeMatrixClient() + ) } @Test diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index bcac6cebd1..edc5744ad8 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers 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.core.asRoomId import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters import io.element.android.libraries.matrix.api.createroom.RoomPreset import io.element.android.libraries.matrix.api.createroom.RoomVisibility @@ -40,9 +41,12 @@ import io.element.android.libraries.matrix.impl.verification.RustSessionVerifica import io.element.android.libraries.sessionstorage.api.SessionStore import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.ClientDelegate import org.matrix.rustcomponents.sdk.RequiredState @@ -180,43 +184,44 @@ class RustMatrixClient constructor( override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result = withContext(dispatchers.io) { runCatching { - val roomId = client.createRoom( - RustCreateRoomParameters( - name = createRoomParams.name, - topic = createRoomParams.topic, - isEncrypted = createRoomParams.isEncrypted, - isDirect = createRoomParams.isDirect, - visibility = when (createRoomParams.visibility) { - RoomVisibility.PUBLIC -> RustRoomVisibility.PUBLIC - RoomVisibility.PRIVATE -> RustRoomVisibility.PRIVATE - }, - preset = when (createRoomParams.preset) { - RoomPreset.PRIVATE_CHAT -> RustRoomPreset.PRIVATE_CHAT - RoomPreset.PUBLIC_CHAT -> RustRoomPreset.PUBLIC_CHAT - RoomPreset.TRUSTED_PRIVATE_CHAT -> RustRoomPreset.TRUSTED_PRIVATE_CHAT - }, - invite = createRoomParams.invite?.map { it.value }, - avatar = createRoomParams.avatar, - ) + val rustParams = RustCreateRoomParameters( + name = createRoomParams.name, + topic = createRoomParams.topic, + isEncrypted = createRoomParams.isEncrypted, + isDirect = createRoomParams.isDirect, + visibility = when (createRoomParams.visibility) { + RoomVisibility.PUBLIC -> RustRoomVisibility.PUBLIC + RoomVisibility.PRIVATE -> RustRoomVisibility.PRIVATE + }, + preset = when (createRoomParams.preset) { + RoomPreset.PRIVATE_CHAT -> RustRoomPreset.PRIVATE_CHAT + RoomPreset.PUBLIC_CHAT -> RustRoomPreset.PUBLIC_CHAT + RoomPreset.TRUSTED_PRIVATE_CHAT -> RustRoomPreset.TRUSTED_PRIVATE_CHAT + }, + invite = createRoomParams.invite?.map { it.value }, + avatar = createRoomParams.avatar, ) - RoomId(roomId) + val roomId = client.createRoom(rustParams).asRoomId() + + // Wait to receive the room back from the sync + withTimeout(30_000L) { + slidingSyncObserverProxy.updateSummaryFlow.filter { roomId.value in it.rooms }.first() + } + + roomId } } - override suspend fun createDM(userId: UserId): Result = withContext(dispatchers.io) { - runCatching { - val roomId = client.createRoom( - RustCreateRoomParameters( - name = null, - isEncrypted = true, - isDirect = true, - visibility = RustRoomVisibility.PRIVATE, - preset = RustRoomPreset.TRUSTED_PRIVATE_CHAT, - invite = listOf(userId.value), - ) - ) - RoomId(roomId) - } + override suspend fun createDM(userId: UserId): Result { + val createRoomParams = CreateRoomParameters( + name = null, + isEncrypted = true, + isDirect = true, + visibility = RoomVisibility.PRIVATE, + preset = RoomPreset.TRUSTED_PRIVATE_CHAT, + invite = listOf(userId) + ) + return createRoom(createRoomParams) } override fun mediaResolver(): MediaResolver = mediaResolver @@ -321,3 +326,4 @@ class RustMatrixClient constructor( return sessionDirectory.deleteRecursively() } } + diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 72c7dfe08f..379dd5508f 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -121,60 +121,15 @@ "Are you sure that you want to leave this room? This room is not public and you will not be able to rejoin without an invite." "Are you sure that you want to leave the room?" "%1$s Android" - "Call" - "Listening for events" - "Noisy notifications" - "Silent notifications" - "** Failed to send - please open room" - "Join" - "Reject" - "New Messages" - "Mark as read" - "Quick reply" - "Me" - "You are viewing the notification! Click me!" - "%1$s: %2$s" - "%1$s: %2$s %3$s" - "%1$s and %2$s" - "%1$s in %2$s" - "%1$s in %2$s and %3$s" "%1$d member" "%1$d members" - - "%1$s: %2$d message" - "%1$s: %2$d messages" - - - "%d notification" - "%d notifications" - - - "%d invitation" - "%d invitations" - - - "%d new message" - "%d new messages" - - - "%d unread notified message" - "%d unread notified messages" - - - "%d room" - "%d rooms" - "%1$d room change" "%1$d room changes" "Rageshake to report bug" - "Choose how to receive notifications" - "Background synchronization" - "Google Services" - "No valid Google Play Services found. Notifications may not work properly." "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?" "This message will be reported to your homeserver’s administrator. They will not be able to read any encrypted messages." "Reason for reporting this content" From 0718e6feecfe25f9d3d124ffde1ae442633b31ab Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Apr 2023 14:06:12 +0200 Subject: [PATCH 2/6] Changelog --- changelog.d/111.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/111.feature diff --git a/changelog.d/111.feature b/changelog.d/111.feature new file mode 100644 index 0000000000..865bb39cbd --- /dev/null +++ b/changelog.d/111.feature @@ -0,0 +1 @@ +[Create and join rooms] Create a room and show it From 3c9af936abe48619438e88a7f12a7f0f04e618ed Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Apr 2023 16:26:14 +0200 Subject: [PATCH 3/6] Add tests on create action --- .../ConfigureRoomPresenterTests.kt | 56 ++++++++++++++++++- .../libraries/matrix/test/FakeMatrixClient.kt | 7 ++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt index 01e5b175da..3f467b1d40 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt @@ -26,9 +26,12 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.createroom.impl.CreateRoomConfig import io.element.android.features.createroom.impl.CreateRoomDataStore import io.element.android.features.userlist.api.UserListDataStore +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_ROOM_NAME +import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.ui.components.aMatrixUser import kotlinx.collections.immutable.persistentListOf @@ -45,13 +48,15 @@ class ConfigureRoomPresenterTests { private lateinit var presenter: ConfigureRoomPresenter private lateinit var userListDataStore: UserListDataStore + private lateinit var fakeMatrixClient: FakeMatrixClient @Before fun setup() { + fakeMatrixClient = FakeMatrixClient() userListDataStore = UserListDataStore() presenter = ConfigureRoomPresenter( dataStore = CreateRoomDataStore(userListDataStore), - matrixClient = FakeMatrixClient() + matrixClient = fakeMatrixClient ) } @@ -153,5 +158,54 @@ class ConfigureRoomPresenterTests { assertThat(newState.config).isEqualTo(expectedConfig) } } + + @Test + fun `present - trigger create room action`() = runTest { + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val createRoomResult = Result.success(RoomId("!createRoomResult")) + + fakeMatrixClient.givenCreateRoomResult(createRoomResult) + + initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config)) + assertThat(awaitItem().createRoomAction).isInstanceOf(Async.Loading::class.java) + val stateAfterCreateRoom = awaitItem() + assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(Async.Success::class.java) + assertThat(stateAfterCreateRoom.createRoomAction.dataOrNull()).isEqualTo(createRoomResult.getOrNull()) + } + } + + @Test + fun `present - trigger retry and cancel actions`() = runTest { + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val createRoomResult = Result.failure(A_THROWABLE) + + fakeMatrixClient.givenCreateRoomResult(createRoomResult) + + // Create + initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config)) + assertThat(awaitItem().createRoomAction).isInstanceOf(Async.Loading::class.java) + val stateAfterCreateRoom = awaitItem() + assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(Async.Failure::class.java) + assertThat((stateAfterCreateRoom.createRoomAction as? Async.Failure)?.error).isEqualTo(createRoomResult.exceptionOrNull()) + + // Retry + stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config)) + assertThat(awaitItem().createRoomAction).isInstanceOf(Async.Uninitialized::class.java) + assertThat(awaitItem().createRoomAction).isInstanceOf(Async.Loading::class.java) + val stateAfterRetry = awaitItem() + assertThat(stateAfterRetry.createRoomAction).isInstanceOf(Async.Failure::class.java) + assertThat((stateAfterRetry.createRoomAction as? Async.Failure)?.error).isEqualTo(createRoomResult.exceptionOrNull()) + + // Cancel + stateAfterRetry.eventSink(ConfigureRoomEvents.CancelCreateRoom) + assertThat(awaitItem().createRoomAction).isInstanceOf(Async.Uninitialized::class.java) + } + } } 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 a6741982ea..0515b37660 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 @@ -46,6 +46,7 @@ class FakeMatrixClient( private val notificationService: FakeNotificationService = FakeNotificationService(), ) : MatrixClient { + private var createRoomResult: Result = Result.success(A_ROOM_ID) private var createDmResult: Result = Result.success(A_ROOM_ID) private var createDmFailure: Throwable? = null private var findDmResult: MatrixRoom? = FakeMatrixRoom() @@ -61,7 +62,7 @@ class FakeMatrixClient( override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result { delay(100) - return Result.success(A_ROOM_ID) + return createRoomResult } override suspend fun createDM(userId: UserId): Result { @@ -119,6 +120,10 @@ class FakeMatrixClient( logoutFailure = failure } + fun givenCreateRoomResult(result: Result) { + createRoomResult = result + } + fun givenCreateDmResult(result: Result) { createDmResult = result } From 9fcbab851a9101b38c5a0c608d961a982e091fd2 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Apr 2023 16:46:45 +0200 Subject: [PATCH 4/6] Rename callbacks for clarity and consistency --- .../kotlin/io/element/android/appnav/LoggedInFlowNode.kt | 5 +---- .../features/createroom/api/CreateRoomEntryPoint.kt | 2 +- .../features/createroom/impl/CreateRoomFlowNode.kt | 8 ++++---- .../createroom/impl/configureroom/ConfigureRoomNode.kt | 8 ++++---- .../features/createroom/impl/root/CreateRoomRootNode.kt | 8 ++++---- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 6f9319f923..5aee5cb22c 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -56,10 +56,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.ui.di.MatrixUIBindings import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking import kotlinx.parcelize.Parcelize -import kotlin.coroutines.coroutineContext @ContributesNode(AppScope::class) class LoggedInFlowNode @AssistedInject constructor( @@ -201,7 +198,7 @@ class LoggedInFlowNode @AssistedInject constructor( } NavTarget.CreateRoom -> { val callback = object : CreateRoomEntryPoint.Callback { - override fun onOpenRoom(roomId: RoomId) { + override fun onSuccess(roomId: RoomId) { backstack.replace(NavTarget.Room(roomId)) } } diff --git a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt index 73f5110daa..18e0e4e28f 100644 --- a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt +++ b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt @@ -31,6 +31,6 @@ interface CreateRoomEntryPoint : FeatureEntryPoint { } interface Callback : Plugin { - fun onOpenRoom(roomId: RoomId) + fun onSuccess(roomId: RoomId) } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt index 1376a7270f..6f447e6bc9 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt @@ -68,16 +68,16 @@ class CreateRoomFlowNode @AssistedInject constructor( backstack.push(NavTarget.NewRoom) } - override fun onOpenRoom(roomId: RoomId) { - plugins().forEach { it.onOpenRoom(roomId) } + override fun onStartChatSuccess(roomId: RoomId) { + plugins().forEach { it.onSuccess(roomId) } } } createNode(context = buildContext, plugins = listOf(callback)) } NavTarget.NewRoom -> { val callback = object : ConfigureRoomNode.Callback { - override fun onRoomCreated(roomId: RoomId) { - plugins().forEach { it.onOpenRoom(roomId) } + override fun onCreateRoomSuccess(roomId: RoomId) { + plugins().forEach { it.onSuccess(roomId) } } } createNode(context = buildContext, plugins = listOf(callback)) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt index aaa6f9667a..060bb447e7 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt @@ -36,12 +36,12 @@ class ConfigureRoomNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onRoomCreated(roomId: RoomId) + fun onCreateRoomSuccess(roomId: RoomId) } private val callback = object : Callback { - override fun onRoomCreated(roomId: RoomId) { - plugins().forEach { it.onRoomCreated(roomId) } + override fun onCreateRoomSuccess(roomId: RoomId) { + plugins().forEach { it.onCreateRoomSuccess(roomId) } } } @@ -52,7 +52,7 @@ class ConfigureRoomNode @AssistedInject constructor( state = state, modifier = modifier, onBackPressed = this::navigateUp, - onRoomCreated = callback::onRoomCreated + onRoomCreated = callback::onCreateRoomSuccess ) } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootNode.kt index 3a714a0eb3..596255dc15 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootNode.kt @@ -37,7 +37,7 @@ class CreateRoomRootNode @AssistedInject constructor( interface Callback : Plugin { fun onCreateNewRoom() - fun onOpenRoom(roomId: RoomId) + fun onStartChatSuccess(roomId: RoomId) } private val callback = object : Callback { @@ -45,8 +45,8 @@ class CreateRoomRootNode @AssistedInject constructor( plugins().forEach { it.onCreateNewRoom() } } - override fun onOpenRoom(roomId: RoomId) { - plugins().forEach { it.onOpenRoom(roomId) } + override fun onStartChatSuccess(roomId: RoomId) { + plugins().forEach { it.onStartChatSuccess(roomId) } } } @@ -58,7 +58,7 @@ class CreateRoomRootNode @AssistedInject constructor( modifier = modifier, onClosePressed = this::navigateUp, onNewRoomClicked = callback::onCreateNewRoom, - onOpenDM = callback::onOpenRoom, + onOpenDM = callback::onStartChatSuccess, ) } } From 9c56c62548cce4db4179455335401218bb0628d9 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Apr 2023 16:49:11 +0200 Subject: [PATCH 5/6] move instruction --- .../createroom/impl/configureroom/ConfigureRoomPresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt index 99d093a4ee..79ae6a7f7a 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt @@ -56,7 +56,6 @@ class ConfigureRoomPresenter @Inject constructor( val createRoomAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } fun createRoom(config: CreateRoomConfig) { - createRoomAction.value = Async.Uninitialized localCoroutineScope.createRoom(config, createRoomAction) } @@ -81,6 +80,7 @@ class ConfigureRoomPresenter @Inject constructor( } private fun CoroutineScope.createRoom(config: CreateRoomConfig, createRoomAction: MutableState>) = launch { + createRoomAction.value = Async.Uninitialized suspend { val params = CreateRoomParameters( name = config.roomName, From 509299b1d276a5161b53d67b257b8715a5cf72eb Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Apr 2023 17:27:47 +0200 Subject: [PATCH 6/6] Revert "move instruction" This reverts commit 9c56c62548cce4db4179455335401218bb0628d9. --- .../createroom/impl/configureroom/ConfigureRoomPresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt index 79ae6a7f7a..99d093a4ee 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt @@ -56,6 +56,7 @@ class ConfigureRoomPresenter @Inject constructor( val createRoomAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } fun createRoom(config: CreateRoomConfig) { + createRoomAction.value = Async.Uninitialized localCoroutineScope.createRoom(config, createRoomAction) } @@ -80,7 +81,6 @@ class ConfigureRoomPresenter @Inject constructor( } private fun CoroutineScope.createRoom(config: CreateRoomConfig, createRoomAction: MutableState>) = launch { - createRoomAction.value = Async.Uninitialized suspend { val params = CreateRoomParameters( name = config.roomName,