From 04ad36641d3a1fc306d496299d559b929a9854ba Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 14 Apr 2023 00:20:33 +0200 Subject: [PATCH] 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"