Merge pull request #324 from vector-im/feature/fre/create_room
Handle create room action
This commit is contained in:
commit
c2927140a6
17 changed files with 213 additions and 97 deletions
|
|
@ -199,7 +199,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
NavTarget.CreateRoom -> {
|
NavTarget.CreateRoom -> {
|
||||||
val callback = object : CreateRoomEntryPoint.Callback {
|
val callback = object : CreateRoomEntryPoint.Callback {
|
||||||
override fun onOpenRoom(roomId: RoomId) {
|
override fun onSuccess(roomId: RoomId) {
|
||||||
backstack.replace(NavTarget.Room(roomId))
|
backstack.replace(NavTarget.Room(roomId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
changelog.d/111.feature
Normal file
1
changelog.d/111.feature
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
[Create and join rooms] Create a room and show it
|
||||||
|
|
@ -31,6 +31,6 @@ interface CreateRoomEntryPoint : FeatureEntryPoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onOpenRoom(roomId: RoomId)
|
fun onSuccess(roomId: RoomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,10 +76,10 @@ class ConfigureRoomFlowNode @AssistedInject constructor(
|
||||||
backstack.push(NavTarget.ConfigureRoom)
|
backstack.push(NavTarget.ConfigureRoom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createNode<AddPeopleNode>(context = buildContext, plugins = listOf(callback))
|
createNode<AddPeopleNode>(context = buildContext, plugins = plugins.plus(callback))
|
||||||
}
|
}
|
||||||
NavTarget.ConfigureRoom -> {
|
NavTarget.ConfigureRoom -> {
|
||||||
createNode<ConfigureRoomNode>(context = buildContext)
|
createNode<ConfigureRoomNode>(context = buildContext, plugins = plugins)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
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.features.createroom.impl.root.CreateRoomRootNode
|
||||||
import io.element.android.libraries.architecture.BackstackNode
|
import io.element.android.libraries.architecture.BackstackNode
|
||||||
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
||||||
|
|
@ -67,14 +68,19 @@ class CreateRoomFlowNode @AssistedInject constructor(
|
||||||
backstack.push(NavTarget.NewRoom)
|
backstack.push(NavTarget.NewRoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOpenRoom(roomId: RoomId) {
|
override fun onStartChatSuccess(roomId: RoomId) {
|
||||||
plugins<CreateRoomEntryPoint.Callback>().forEach { it.onOpenRoom(roomId) }
|
plugins<CreateRoomEntryPoint.Callback>().forEach { it.onSuccess(roomId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createNode<CreateRoomRootNode>(context = buildContext, plugins = listOf(callback))
|
createNode<CreateRoomRootNode>(context = buildContext, plugins = listOf(callback))
|
||||||
}
|
}
|
||||||
NavTarget.NewRoom -> {
|
NavTarget.NewRoom -> {
|
||||||
createNode<ConfigureRoomFlowNode>(context = buildContext, plugins = emptyList())
|
val callback = object : ConfigureRoomNode.Callback {
|
||||||
|
override fun onCreateRoomSuccess(roomId: RoomId) {
|
||||||
|
plugins<CreateRoomEntryPoint.Callback>().forEach { it.onSuccess(roomId) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createNode<ConfigureRoomFlowNode>(context = buildContext, plugins = listOf(callback))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.features.createroom.impl.configureroom
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import io.element.android.features.createroom.impl.CreateRoomConfig
|
||||||
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
|
|
||||||
sealed interface ConfigureRoomEvents {
|
sealed interface ConfigureRoomEvents {
|
||||||
|
|
@ -25,5 +26,6 @@ sealed interface ConfigureRoomEvents {
|
||||||
data class AvatarUriChanged(val uri: Uri?) : ConfigureRoomEvents
|
data class AvatarUriChanged(val uri: Uri?) : ConfigureRoomEvents
|
||||||
data class RoomPrivacyChanged(val privacy: RoomPrivacy?) : ConfigureRoomEvents
|
data class RoomPrivacyChanged(val privacy: RoomPrivacy?) : ConfigureRoomEvents
|
||||||
data class RemoveFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents
|
data class RemoveFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents
|
||||||
object CreateRoom : ConfigureRoomEvents
|
data class CreateRoom(val config: CreateRoomConfig) : ConfigureRoomEvents
|
||||||
|
object CancelCreateRoom : ConfigureRoomEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,12 @@ import androidx.compose.ui.Modifier
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
|
import com.bumble.appyx.core.plugin.plugins
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.features.createroom.impl.di.CreateRoomScope
|
import io.element.android.features.createroom.impl.di.CreateRoomScope
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
@ContributesNode(CreateRoomScope::class)
|
@ContributesNode(CreateRoomScope::class)
|
||||||
class ConfigureRoomNode @AssistedInject constructor(
|
class ConfigureRoomNode @AssistedInject constructor(
|
||||||
|
|
@ -33,13 +35,24 @@ class ConfigureRoomNode @AssistedInject constructor(
|
||||||
private val presenter: ConfigureRoomPresenter,
|
private val presenter: ConfigureRoomPresenter,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
|
interface Callback : Plugin {
|
||||||
|
fun onCreateRoomSuccess(roomId: RoomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val callback = object : Callback {
|
||||||
|
override fun onCreateRoomSuccess(roomId: RoomId) {
|
||||||
|
plugins<Callback>().forEach { it.onCreateRoomSuccess(roomId) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
ConfigureRoomView(
|
ConfigureRoomView(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onBackPressed = { navigateUp() } // TODO we should keep in memory the current view state
|
onBackPressed = this::navigateUp,
|
||||||
|
onRoomCreated = callback::onCreateRoomSuccess
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,17 +17,30 @@
|
||||||
package io.element.android.features.createroom.impl.configureroom
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
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.CreateRoomConfig
|
||||||
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
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.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
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ConfigureRoomPresenter @Inject constructor(
|
class ConfigureRoomPresenter @Inject constructor(
|
||||||
private val dataStore: CreateRoomDataStore,
|
private val dataStore: CreateRoomDataStore,
|
||||||
|
private val matrixClient: MatrixClient,
|
||||||
) : Presenter<ConfigureRoomState> {
|
) : Presenter<ConfigureRoomState> {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -39,6 +52,14 @@ class ConfigureRoomPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
val createRoomAction: MutableState<Async<RoomId>> = remember { mutableStateOf(Async.Uninitialized) }
|
||||||
|
|
||||||
|
fun createRoom(config: CreateRoomConfig) {
|
||||||
|
createRoomAction.value = Async.Uninitialized
|
||||||
|
localCoroutineScope.createRoom(config, createRoomAction)
|
||||||
|
}
|
||||||
|
|
||||||
fun handleEvents(event: ConfigureRoomEvents) {
|
fun handleEvents(event: ConfigureRoomEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is ConfigureRoomEvents.AvatarUriChanged -> dataStore.setAvatarUrl(event.uri?.toString())
|
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.TopicChanged -> dataStore.setTopic(event.topic)
|
||||||
is ConfigureRoomEvents.RoomPrivacyChanged -> dataStore.setPrivacy(event.privacy)
|
is ConfigureRoomEvents.RoomPrivacyChanged -> dataStore.setPrivacy(event.privacy)
|
||||||
is ConfigureRoomEvents.RemoveFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser)
|
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(
|
return ConfigureRoomState(
|
||||||
createRoomConfig.value,
|
config = createRoomConfig.value,
|
||||||
isCreateButtonEnabled = isCreateButtonEnabled,
|
isCreateButtonEnabled = isCreateButtonEnabled,
|
||||||
|
createRoomAction = createRoomAction.value,
|
||||||
eventSink = ::handleEvents,
|
eventSink = ::handleEvents,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.createRoom(config: CreateRoomConfig, createRoomAction: MutableState<Async<RoomId>>) = 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,12 @@
|
||||||
package io.element.android.features.createroom.impl.configureroom
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
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(
|
data class ConfigureRoomState(
|
||||||
val config: CreateRoomConfig,
|
val config: CreateRoomConfig,
|
||||||
val isCreateButtonEnabled: Boolean,
|
val isCreateButtonEnabled: Boolean,
|
||||||
|
val createRoomAction: Async<RoomId>,
|
||||||
val eventSink: (ConfigureRoomEvents) -> Unit
|
val eventSink: (ConfigureRoomEvents) -> Unit
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.createroom.impl.configureroom
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
import io.element.android.features.createroom.impl.CreateRoomConfig
|
||||||
import io.element.android.features.userlist.api.aListOfSelectedUsers
|
import io.element.android.features.userlist.api.aListOfSelectedUsers
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
|
|
||||||
open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomState> {
|
open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomState> {
|
||||||
override val values: Sequence<ConfigureRoomState>
|
override val values: Sequence<ConfigureRoomState>
|
||||||
|
|
@ -39,5 +40,6 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
|
||||||
fun aConfigureRoomState() = ConfigureRoomState(
|
fun aConfigureRoomState() = ConfigureRoomState(
|
||||||
config = CreateRoomConfig(),
|
config = CreateRoomConfig(),
|
||||||
isCreateButtonEnabled = false,
|
isCreateButtonEnabled = false,
|
||||||
|
createRoomAction = Async.Uninitialized,
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.selection.selectableGroup
|
import androidx.compose.foundation.selection.selectableGroup
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
@ -44,13 +45,17 @@ import io.element.android.features.createroom.impl.components.Avatar
|
||||||
import io.element.android.features.createroom.impl.components.LabelledTextField
|
import io.element.android.features.createroom.impl.components.LabelledTextField
|
||||||
import io.element.android.features.createroom.impl.components.RoomPrivacyOption
|
import io.element.android.features.createroom.impl.components.RoomPrivacyOption
|
||||||
import io.element.android.features.userlist.api.components.SelectedUsersList
|
import io.element.android.features.userlist.api.components.SelectedUsersList
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
|
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
import io.element.android.libraries.designsystem.theme.components.CenterAlignedTopAppBar
|
import io.element.android.libraries.designsystem.theme.components.CenterAlignedTopAppBar
|
||||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.ui.strings.R as StringR
|
import io.element.android.libraries.ui.strings.R as StringR
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -58,7 +63,14 @@ fun ConfigureRoomView(
|
||||||
state: ConfigureRoomState,
|
state: ConfigureRoomState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onBackPressed: () -> Unit = {},
|
onBackPressed: () -> Unit = {},
|
||||||
|
onRoomCreated: (RoomId) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
|
if (state.createRoomAction is Async.Success) {
|
||||||
|
LaunchedEffect(state.createRoomAction) {
|
||||||
|
onRoomCreated(state.createRoomAction.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
@ -67,8 +79,7 @@ fun ConfigureRoomView(
|
||||||
isNextActionEnabled = state.isCreateButtonEnabled,
|
isNextActionEnabled = state.isCreateButtonEnabled,
|
||||||
onBackPressed = onBackPressed,
|
onBackPressed = onBackPressed,
|
||||||
onNextPressed = {
|
onNextPressed = {
|
||||||
// state.eventSink(ConfigureRoomEvents.CreateRoom)
|
state.eventSink(ConfigureRoomEvents.CreateRoom(state.config))
|
||||||
Toast.makeText(context, "not implemented yet", Toast.LENGTH_SHORT).show()
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class CreateRoomRootNode @AssistedInject constructor(
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onCreateNewRoom()
|
fun onCreateNewRoom()
|
||||||
fun onOpenRoom(roomId: RoomId)
|
fun onStartChatSuccess(roomId: RoomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val callback = object : Callback {
|
private val callback = object : Callback {
|
||||||
|
|
@ -45,8 +45,8 @@ class CreateRoomRootNode @AssistedInject constructor(
|
||||||
plugins<Callback>().forEach { it.onCreateNewRoom() }
|
plugins<Callback>().forEach { it.onCreateNewRoom() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOpenRoom(roomId: RoomId) {
|
override fun onStartChatSuccess(roomId: RoomId) {
|
||||||
plugins<Callback>().forEach { it.onOpenRoom(roomId) }
|
plugins<Callback>().forEach { it.onStartChatSuccess(roomId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ class CreateRoomRootNode @AssistedInject constructor(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onClosePressed = this::navigateUp,
|
onClosePressed = this::navigateUp,
|
||||||
onNewRoomClicked = callback::onCreateNewRoom,
|
onNewRoomClicked = callback::onCreateNewRoom,
|
||||||
onOpenDM = callback::onOpenRoom,
|
onOpenDM = callback::onStartChatSuccess,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
<string name="screen_create_room_action_create_room">"New room"</string>
|
<string name="screen_create_room_action_create_room">"New room"</string>
|
||||||
<string name="screen_create_room_action_invite_people">"Invite people"</string>
|
<string name="screen_create_room_action_invite_people">"Invite people"</string>
|
||||||
<string name="screen_create_room_add_people_title">"Add people"</string>
|
<string name="screen_create_room_add_people_title">"Add people"</string>
|
||||||
|
<string name="screen_create_room_error_creating_room">"An error occurred when creating the room"</string>
|
||||||
<string name="screen_create_room_private_option_description">"Messages in this room are encrypted. Encryption can’t be disabled afterwards."</string>
|
<string name="screen_create_room_private_option_description">"Messages in this room are encrypted. Encryption can’t be disabled afterwards."</string>
|
||||||
<string name="screen_create_room_private_option_title">"Private room (invite only)"</string>
|
<string name="screen_create_room_private_option_title">"Private room (invite only)"</string>
|
||||||
<string name="screen_create_room_public_option_description">"Messages are not encrypted and anyone can read them. You can enable encryption at a later date."</string>
|
<string name="screen_create_room_public_option_description">"Messages are not encrypted and anyone can read them. You can enable encryption at a later date."</string>
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,13 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
import io.element.android.features.createroom.impl.CreateRoomConfig
|
||||||
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
||||||
import io.element.android.features.userlist.api.UserListDataStore
|
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.AN_AVATAR_URL
|
||||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
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_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 io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
@ -44,11 +48,16 @@ class ConfigureRoomPresenterTests {
|
||||||
|
|
||||||
private lateinit var presenter: ConfigureRoomPresenter
|
private lateinit var presenter: ConfigureRoomPresenter
|
||||||
private lateinit var userListDataStore: UserListDataStore
|
private lateinit var userListDataStore: UserListDataStore
|
||||||
|
private lateinit var fakeMatrixClient: FakeMatrixClient
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
|
fakeMatrixClient = FakeMatrixClient()
|
||||||
userListDataStore = UserListDataStore()
|
userListDataStore = UserListDataStore()
|
||||||
presenter = ConfigureRoomPresenter(CreateRoomDataStore(userListDataStore))
|
presenter = ConfigureRoomPresenter(
|
||||||
|
dataStore = CreateRoomDataStore(userListDataStore),
|
||||||
|
matrixClient = fakeMatrixClient
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -149,5 +158,54 @@ class ConfigureRoomPresenterTests {
|
||||||
assertThat(newState.config).isEqualTo(expectedConfig)
|
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<RoomId>(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
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.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.CreateRoomParameters
|
||||||
import io.element.android.libraries.matrix.api.createroom.RoomPreset
|
import io.element.android.libraries.matrix.api.createroom.RoomPreset
|
||||||
import io.element.android.libraries.matrix.api.createroom.RoomVisibility
|
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 io.element.android.libraries.sessionstorage.api.SessionStore
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
import org.matrix.rustcomponents.sdk.Client
|
import org.matrix.rustcomponents.sdk.Client
|
||||||
import org.matrix.rustcomponents.sdk.ClientDelegate
|
import org.matrix.rustcomponents.sdk.ClientDelegate
|
||||||
import org.matrix.rustcomponents.sdk.RequiredState
|
import org.matrix.rustcomponents.sdk.RequiredState
|
||||||
|
|
@ -180,43 +184,44 @@ class RustMatrixClient constructor(
|
||||||
|
|
||||||
override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> = withContext(dispatchers.io) {
|
override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> = withContext(dispatchers.io) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val roomId = client.createRoom(
|
val rustParams = RustCreateRoomParameters(
|
||||||
RustCreateRoomParameters(
|
name = createRoomParams.name,
|
||||||
name = createRoomParams.name,
|
topic = createRoomParams.topic,
|
||||||
topic = createRoomParams.topic,
|
isEncrypted = createRoomParams.isEncrypted,
|
||||||
isEncrypted = createRoomParams.isEncrypted,
|
isDirect = createRoomParams.isDirect,
|
||||||
isDirect = createRoomParams.isDirect,
|
visibility = when (createRoomParams.visibility) {
|
||||||
visibility = when (createRoomParams.visibility) {
|
RoomVisibility.PUBLIC -> RustRoomVisibility.PUBLIC
|
||||||
RoomVisibility.PUBLIC -> RustRoomVisibility.PUBLIC
|
RoomVisibility.PRIVATE -> RustRoomVisibility.PRIVATE
|
||||||
RoomVisibility.PRIVATE -> RustRoomVisibility.PRIVATE
|
},
|
||||||
},
|
preset = when (createRoomParams.preset) {
|
||||||
preset = when (createRoomParams.preset) {
|
RoomPreset.PRIVATE_CHAT -> RustRoomPreset.PRIVATE_CHAT
|
||||||
RoomPreset.PRIVATE_CHAT -> RustRoomPreset.PRIVATE_CHAT
|
RoomPreset.PUBLIC_CHAT -> RustRoomPreset.PUBLIC_CHAT
|
||||||
RoomPreset.PUBLIC_CHAT -> RustRoomPreset.PUBLIC_CHAT
|
RoomPreset.TRUSTED_PRIVATE_CHAT -> RustRoomPreset.TRUSTED_PRIVATE_CHAT
|
||||||
RoomPreset.TRUSTED_PRIVATE_CHAT -> RustRoomPreset.TRUSTED_PRIVATE_CHAT
|
},
|
||||||
},
|
invite = createRoomParams.invite?.map { it.value },
|
||||||
invite = createRoomParams.invite?.map { it.value },
|
avatar = createRoomParams.avatar,
|
||||||
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<RoomId> = withContext(dispatchers.io) {
|
override suspend fun createDM(userId: UserId): Result<RoomId> {
|
||||||
runCatching {
|
val createRoomParams = CreateRoomParameters(
|
||||||
val roomId = client.createRoom(
|
name = null,
|
||||||
RustCreateRoomParameters(
|
isEncrypted = true,
|
||||||
name = null,
|
isDirect = true,
|
||||||
isEncrypted = true,
|
visibility = RoomVisibility.PRIVATE,
|
||||||
isDirect = true,
|
preset = RoomPreset.TRUSTED_PRIVATE_CHAT,
|
||||||
visibility = RustRoomVisibility.PRIVATE,
|
invite = listOf(userId)
|
||||||
preset = RustRoomPreset.TRUSTED_PRIVATE_CHAT,
|
)
|
||||||
invite = listOf(userId.value),
|
return createRoom(createRoomParams)
|
||||||
)
|
|
||||||
)
|
|
||||||
RoomId(roomId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mediaResolver(): MediaResolver = mediaResolver
|
override fun mediaResolver(): MediaResolver = mediaResolver
|
||||||
|
|
@ -321,3 +326,4 @@ class RustMatrixClient constructor(
|
||||||
return sessionDirectory.deleteRecursively()
|
return sessionDirectory.deleteRecursively()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ class FakeMatrixClient(
|
||||||
private val notificationService: FakeNotificationService = FakeNotificationService(),
|
private val notificationService: FakeNotificationService = FakeNotificationService(),
|
||||||
) : MatrixClient {
|
) : MatrixClient {
|
||||||
|
|
||||||
|
private var createRoomResult: Result<RoomId> = Result.success(A_ROOM_ID)
|
||||||
private var createDmResult: Result<RoomId> = Result.success(A_ROOM_ID)
|
private var createDmResult: Result<RoomId> = Result.success(A_ROOM_ID)
|
||||||
private var createDmFailure: Throwable? = null
|
private var createDmFailure: Throwable? = null
|
||||||
private var findDmResult: MatrixRoom? = FakeMatrixRoom()
|
private var findDmResult: MatrixRoom? = FakeMatrixRoom()
|
||||||
|
|
@ -61,7 +62,7 @@ class FakeMatrixClient(
|
||||||
|
|
||||||
override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> {
|
override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> {
|
||||||
delay(100)
|
delay(100)
|
||||||
return Result.success(A_ROOM_ID)
|
return createRoomResult
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun createDM(userId: UserId): Result<RoomId> {
|
override suspend fun createDM(userId: UserId): Result<RoomId> {
|
||||||
|
|
@ -119,6 +120,10 @@ class FakeMatrixClient(
|
||||||
logoutFailure = failure
|
logoutFailure = failure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenCreateRoomResult(result: Result<RoomId>) {
|
||||||
|
createRoomResult = result
|
||||||
|
}
|
||||||
|
|
||||||
fun givenCreateDmResult(result: Result<RoomId>) {
|
fun givenCreateDmResult(result: Result<RoomId>) {
|
||||||
createDmResult = result
|
createDmResult = result
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,60 +121,15 @@
|
||||||
<string name="leave_room_alert_private_subtitle">"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."</string>
|
<string name="leave_room_alert_private_subtitle">"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."</string>
|
||||||
<string name="leave_room_alert_subtitle">"Are you sure that you want to leave the room?"</string>
|
<string name="leave_room_alert_subtitle">"Are you sure that you want to leave the room?"</string>
|
||||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||||
<string name="notification_channel_call">"Call"</string>
|
|
||||||
<string name="notification_channel_listening_for_events">"Listening for events"</string>
|
|
||||||
<string name="notification_channel_noisy">"Noisy notifications"</string>
|
|
||||||
<string name="notification_channel_silent">"Silent notifications"</string>
|
|
||||||
<string name="notification_inline_reply_failed">"** Failed to send - please open room"</string>
|
|
||||||
<string name="notification_invitation_action_join">"Join"</string>
|
|
||||||
<string name="notification_invitation_action_reject">"Reject"</string>
|
|
||||||
<string name="notification_new_messages">"New Messages"</string>
|
|
||||||
<string name="notification_room_action_mark_as_read">"Mark as read"</string>
|
|
||||||
<string name="notification_room_action_quick_reply">"Quick reply"</string>
|
|
||||||
<string name="notification_sender_me">"Me"</string>
|
|
||||||
<string name="notification_test_push_notification_content">"You are viewing the notification! Click me!"</string>
|
|
||||||
<string name="notification_ticker_text_dm">"%1$s: %2$s"</string>
|
|
||||||
<string name="notification_ticker_text_group">"%1$s: %2$s %3$s"</string>
|
|
||||||
<string name="notification_unread_notified_messages_and_invitation">"%1$s and %2$s"</string>
|
|
||||||
<string name="notification_unread_notified_messages_in_room">"%1$s in %2$s"</string>
|
|
||||||
<string name="notification_unread_notified_messages_in_room_and_invitation">"%1$s in %2$s and %3$s"</string>
|
|
||||||
<plurals name="common_member_count">
|
<plurals name="common_member_count">
|
||||||
<item quantity="one">"%1$d member"</item>
|
<item quantity="one">"%1$d member"</item>
|
||||||
<item quantity="other">"%1$d members"</item>
|
<item quantity="other">"%1$d members"</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="notification_compat_summary_line_for_room">
|
|
||||||
<item quantity="one">"%1$s: %2$d message"</item>
|
|
||||||
<item quantity="other">"%1$s: %2$d messages"</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="notification_compat_summary_title">
|
|
||||||
<item quantity="one">"%d notification"</item>
|
|
||||||
<item quantity="other">"%d notifications"</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="notification_invitations">
|
|
||||||
<item quantity="one">"%d invitation"</item>
|
|
||||||
<item quantity="other">"%d invitations"</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="notification_new_messages_for_room">
|
|
||||||
<item quantity="one">"%d new message"</item>
|
|
||||||
<item quantity="other">"%d new messages"</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="notification_unread_notified_messages">
|
|
||||||
<item quantity="one">"%d unread notified message"</item>
|
|
||||||
<item quantity="other">"%d unread notified messages"</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="notification_unread_notified_messages_in_room_rooms">
|
|
||||||
<item quantity="one">"%d room"</item>
|
|
||||||
<item quantity="other">"%d rooms"</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="room_timeline_state_changes">
|
<plurals name="room_timeline_state_changes">
|
||||||
<item quantity="one">"%1$d room change"</item>
|
<item quantity="one">"%1$d room change"</item>
|
||||||
<item quantity="other">"%1$d room changes"</item>
|
<item quantity="other">"%1$d room changes"</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="preference_rageshake">"Rageshake to report bug"</string>
|
<string name="preference_rageshake">"Rageshake to report bug"</string>
|
||||||
<string name="push_choose_distributor_dialog_title_android">"Choose how to receive notifications"</string>
|
|
||||||
<string name="push_distributor_background_sync_android">"Background synchronization"</string>
|
|
||||||
<string name="push_distributor_firebase_android">"Google Services"</string>
|
|
||||||
<string name="push_no_valid_google_play_services_apk_android">"No valid Google Play Services found. Notifications may not work properly."</string>
|
|
||||||
<string name="rageshake_dialog_content">"You seem to be shaking the phone in frustration. Would you like to open the bug report screen?"</string>
|
<string name="rageshake_dialog_content">"You seem to be shaking the phone in frustration. Would you like to open the bug report screen?"</string>
|
||||||
<string name="report_content_explanation">"This message will be reported to your homeserver’s administrator. They will not be able to read any encrypted messages."</string>
|
<string name="report_content_explanation">"This message will be reported to your homeserver’s administrator. They will not be able to read any encrypted messages."</string>
|
||||||
<string name="report_content_hint">"Reason for reporting this content"</string>
|
<string name="report_content_hint">"Reason for reporting this content"</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue