Create or retrieve DM
This commit is contained in:
parent
7aa1c26e2e
commit
6450fc5724
17 changed files with 220 additions and 28 deletions
|
|
@ -32,6 +32,7 @@ import com.bumble.appyx.core.plugin.Plugin
|
||||||
import com.bumble.appyx.core.plugin.plugins
|
import com.bumble.appyx.core.plugin.plugins
|
||||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||||
|
import com.bumble.appyx.navmodel.backstack.operation.replace
|
||||||
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
|
||||||
|
|
@ -178,7 +179,16 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
NavTarget.CreateRoom -> {
|
NavTarget.CreateRoom -> {
|
||||||
createRoomEntryPoint.createNode(this, buildContext)
|
val callback = object : CreateRoomEntryPoint.Callback {
|
||||||
|
override fun onOpenRoom(roomId: RoomId) {
|
||||||
|
backstack.replace(NavTarget.Room(roomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createRoomEntryPoint
|
||||||
|
.nodeBuilder(this, buildContext)
|
||||||
|
.callback(callback)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
NavTarget.VerifySession -> {
|
NavTarget.VerifySession -> {
|
||||||
verifySessionEntryPoint.createNode(this, buildContext)
|
verifySessionEntryPoint.createNode(this, buildContext)
|
||||||
|
|
|
||||||
|
|
@ -24,4 +24,5 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
|
implementation(projects.libraries.matrix.api)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,21 @@
|
||||||
|
|
||||||
package io.element.android.features.createroom.api
|
package io.element.android.features.createroom.api
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.SimpleFeatureEntryPoint
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
|
import com.bumble.appyx.core.node.Node
|
||||||
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
|
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
interface CreateRoomEntryPoint : SimpleFeatureEntryPoint
|
interface CreateRoomEntryPoint : FeatureEntryPoint {
|
||||||
|
|
||||||
|
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||||
|
interface NodeBuilder {
|
||||||
|
fun callback(callback: Callback): NodeBuilder
|
||||||
|
fun build(): Node
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Callback : Plugin {
|
||||||
|
fun onOpenRoom(roomId: RoomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,17 +23,20 @@ import com.bumble.appyx.core.composable.Children
|
||||||
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 com.bumble.appyx.navmodel.backstack.BackStack
|
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||||
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.api.CreateRoomEntryPoint
|
||||||
import io.element.android.features.createroom.impl.addpeople.AddPeopleNode
|
import io.element.android.features.createroom.impl.addpeople.AddPeopleNode
|
||||||
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
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
|
|
@ -64,6 +67,10 @@ class CreateRoomFlowNode @AssistedInject constructor(
|
||||||
override fun onCreateNewRoom() {
|
override fun onCreateNewRoom() {
|
||||||
backstack.push(NavTarget.NewRoom)
|
backstack.push(NavTarget.NewRoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onOpenRoom(roomId: RoomId) {
|
||||||
|
plugins<CreateRoomEntryPoint.Callback>().forEach { it.onOpenRoom(roomId) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
createNode<CreateRoomRootNode>(buildContext, plugins = listOf(callback))
|
createNode<CreateRoomRootNode>(buildContext, plugins = listOf(callback))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.createroom.impl
|
||||||
|
|
||||||
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.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
|
|
@ -26,7 +27,21 @@ import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultCreateRoomEntryPoint @Inject constructor() : CreateRoomEntryPoint {
|
class DefaultCreateRoomEntryPoint @Inject constructor() : CreateRoomEntryPoint {
|
||||||
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
|
|
||||||
return parentNode.createNode<CreateRoomFlowNode>(buildContext)
|
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): CreateRoomEntryPoint.NodeBuilder {
|
||||||
|
|
||||||
|
val plugins = ArrayList<Plugin>()
|
||||||
|
|
||||||
|
return object : CreateRoomEntryPoint.NodeBuilder {
|
||||||
|
|
||||||
|
override fun callback(callback: CreateRoomEntryPoint.Callback): CreateRoomEntryPoint.NodeBuilder {
|
||||||
|
plugins += callback
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun build(): Node {
|
||||||
|
return parentNode.createNode<CreateRoomFlowNode>(buildContext, plugins)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ package io.element.android.features.createroom.impl.root
|
||||||
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
|
|
||||||
sealed interface CreateRoomRootEvents {
|
sealed interface CreateRoomRootEvents {
|
||||||
data class StartDM(val matrixUser: MatrixUser) : CreateRoomRootEvents
|
|
||||||
object InvitePeople : CreateRoomRootEvents
|
object InvitePeople : CreateRoomRootEvents
|
||||||
|
data class SelectUser(val matrixUser: MatrixUser) : CreateRoomRootEvents
|
||||||
|
data class CreateDM(val matrixUser: MatrixUser) : CreateRoomRootEvents
|
||||||
|
object CancelCreateDM : CreateRoomRootEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.root
|
package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
|
|
@ -27,7 +26,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.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import kotlinx.parcelize.Parcelize
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
class CreateRoomRootNode @AssistedInject constructor(
|
class CreateRoomRootNode @AssistedInject constructor(
|
||||||
|
|
@ -38,15 +37,17 @@ class CreateRoomRootNode @AssistedInject constructor(
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onCreateNewRoom()
|
fun onCreateNewRoom()
|
||||||
|
fun onOpenRoom(roomId: RoomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onCreateNewRoom() {
|
private val callback = object : Callback {
|
||||||
plugins<Callback>().forEach { it.onCreateNewRoom() }
|
override fun onCreateNewRoom() {
|
||||||
}
|
plugins<Callback>().forEach { it.onCreateNewRoom() }
|
||||||
|
}
|
||||||
|
|
||||||
sealed interface NavTarget : Parcelable {
|
override fun onOpenRoom(roomId: RoomId) {
|
||||||
@Parcelize
|
plugins<Callback>().forEach { it.onOpenRoom(roomId) }
|
||||||
object Root : NavTarget
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -56,7 +57,8 @@ class CreateRoomRootNode @AssistedInject constructor(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onClosePressed = this::navigateUp,
|
onClosePressed = this::navigateUp,
|
||||||
onNewRoomClicked = this::onCreateNewRoom,
|
onNewRoomClicked = callback::onCreateNewRoom,
|
||||||
|
onOpenDM = callback::onOpenRoom,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,30 @@
|
||||||
package io.element.android.features.createroom.impl.root
|
package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import io.element.android.features.selectusers.api.SelectUsersEvents
|
||||||
import io.element.android.features.selectusers.api.SelectUsersPresenter
|
import io.element.android.features.selectusers.api.SelectUsersPresenter
|
||||||
import io.element.android.features.selectusers.api.SelectUsersPresenterArgs
|
import io.element.android.features.selectusers.api.SelectUsersPresenterArgs
|
||||||
import io.element.android.features.selectusers.api.SelectionMode
|
import io.element.android.features.selectusers.api.SelectionMode
|
||||||
|
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.ui.model.MatrixUser
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
import timber.log.Timber
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class CreateRoomRootPresenter @Inject constructor(
|
class CreateRoomRootPresenter @Inject constructor(
|
||||||
private val presenterFactory: SelectUsersPresenter.Factory,
|
private val presenterFactory: SelectUsersPresenter.Factory,
|
||||||
|
private val matrixClient: MatrixClient,
|
||||||
) : Presenter<CreateRoomRootState> {
|
) : Presenter<CreateRoomRootState> {
|
||||||
|
|
||||||
private val presenter by lazy {
|
private val presenter by lazy {
|
||||||
|
|
@ -37,20 +51,44 @@ class CreateRoomRootPresenter @Inject constructor(
|
||||||
override fun present(): CreateRoomRootState {
|
override fun present(): CreateRoomRootState {
|
||||||
val selectUsersState = presenter.present()
|
val selectUsersState = presenter.present()
|
||||||
|
|
||||||
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
var showCreateDmConfirmationDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
|
val startDmAction: MutableState<Async<RoomId>> = remember { mutableStateOf(Async.Uninitialized) }
|
||||||
|
|
||||||
fun handleEvents(event: CreateRoomRootEvents) {
|
fun handleEvents(event: CreateRoomRootEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is CreateRoomRootEvents.StartDM -> handleStartDM(event.matrixUser)
|
is CreateRoomRootEvents.SelectUser -> {
|
||||||
|
val existingDM = matrixClient.findDM(event.matrixUser.id)
|
||||||
|
if (existingDM == null) {
|
||||||
|
showCreateDmConfirmationDialog = true
|
||||||
|
} else {
|
||||||
|
startDmAction.value = Async.Success(existingDM.roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is CreateRoomRootEvents.CreateDM -> {
|
||||||
|
showCreateDmConfirmationDialog = false
|
||||||
|
localCoroutineScope.createDM(event.matrixUser, startDmAction)
|
||||||
|
}
|
||||||
|
CreateRoomRootEvents.CancelCreateDM -> {
|
||||||
|
showCreateDmConfirmationDialog = false
|
||||||
|
selectUsersState.eventSink(SelectUsersEvents.ClearSelection)
|
||||||
|
}
|
||||||
CreateRoomRootEvents.InvitePeople -> Unit // Todo Handle invite people action
|
CreateRoomRootEvents.InvitePeople -> Unit // Todo Handle invite people action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreateRoomRootState(
|
return CreateRoomRootState(
|
||||||
selectUsersState = selectUsersState,
|
selectUsersState = selectUsersState,
|
||||||
|
showCreateDmConfirmationDialog = showCreateDmConfirmationDialog,
|
||||||
|
startDmAction = startDmAction.value,
|
||||||
eventSink = ::handleEvents,
|
eventSink = ::handleEvents,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleStartDM(matrixUser: MatrixUser) {
|
private fun CoroutineScope.createDM(user: MatrixUser, startDmAction: MutableState<Async<RoomId>>) = launch {
|
||||||
Timber.d("handleStartDM: $matrixUser") // Todo handle start DM action
|
suspend {
|
||||||
|
matrixClient.createDM(user.id).getOrThrow()
|
||||||
|
}.execute(startDmAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,12 @@
|
||||||
package io.element.android.features.createroom.impl.root
|
package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
import io.element.android.features.selectusers.api.SelectUsersState
|
import io.element.android.features.selectusers.api.SelectUsersState
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
data class CreateRoomRootState(
|
data class CreateRoomRootState(
|
||||||
val selectUsersState: SelectUsersState,
|
val selectUsersState: SelectUsersState,
|
||||||
|
val showCreateDmConfirmationDialog: Boolean,
|
||||||
|
val startDmAction: Async<RoomId>,
|
||||||
val eventSink: (CreateRoomRootEvents) -> Unit,
|
val eventSink: (CreateRoomRootEvents) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.selectusers.api.aSelectUsersState
|
import io.element.android.features.selectusers.api.aSelectUsersState
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
|
|
||||||
open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRootState> {
|
open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRootState> {
|
||||||
override val values: Sequence<CreateRoomRootState>
|
override val values: Sequence<CreateRoomRootState>
|
||||||
|
|
@ -28,5 +29,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
|
||||||
|
|
||||||
fun aCreateRoomRootState() = CreateRoomRootState(
|
fun aCreateRoomRootState() = CreateRoomRootState(
|
||||||
eventSink = {},
|
eventSink = {},
|
||||||
selectUsersState = aSelectUsersState(),
|
startDmAction = Async.Uninitialized,
|
||||||
|
showCreateDmConfirmationDialog = false,
|
||||||
|
selectUsersState = aSelectUsersState(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
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.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
|
|
@ -39,6 +40,9 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import io.element.android.features.createroom.impl.R
|
import io.element.android.features.createroom.impl.R
|
||||||
import io.element.android.features.selectusers.api.SelectUsersView
|
import io.element.android.features.selectusers.api.SelectUsersView
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
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
|
||||||
|
|
@ -46,6 +50,8 @@ import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||||
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.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.ui.model.getBestName
|
||||||
import io.element.android.libraries.designsystem.R as DrawableR
|
import io.element.android.libraries.designsystem.R as DrawableR
|
||||||
import io.element.android.libraries.ui.strings.R as StringR
|
import io.element.android.libraries.ui.strings.R as StringR
|
||||||
|
|
||||||
|
|
@ -56,7 +62,14 @@ fun CreateRoomRootView(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClosePressed: () -> Unit = {},
|
onClosePressed: () -> Unit = {},
|
||||||
onNewRoomClicked: () -> Unit = {},
|
onNewRoomClicked: () -> Unit = {},
|
||||||
|
onOpenDM: (RoomId) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
|
if (state.startDmAction is Async.Success) {
|
||||||
|
LaunchedEffect(state.startDmAction) {
|
||||||
|
onOpenDM(state.startDmAction.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
topBar = {
|
topBar = {
|
||||||
|
|
@ -72,7 +85,7 @@ fun CreateRoomRootView(
|
||||||
SelectUsersView(
|
SelectUsersView(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
state = state.selectUsersState,
|
state = state.selectUsersState,
|
||||||
onUserSelected = { state.eventSink.invoke(CreateRoomRootEvents.StartDM(it)) },
|
onUserSelected = { state.eventSink(CreateRoomRootEvents.SelectUser(it)) },
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!state.selectUsersState.isSearchActive) {
|
if (!state.selectUsersState.isSearchActive) {
|
||||||
|
|
@ -83,6 +96,12 @@ fun CreateRoomRootView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CreateDmConfirmationDialog(state)
|
||||||
|
|
||||||
|
if (state.startDmAction is Async.Loading) {
|
||||||
|
ProgressDialog(text = "Creating room...")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
@ -153,6 +172,26 @@ fun CreateRoomActionButton(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CreateDmConfirmationDialog(
|
||||||
|
state: CreateRoomRootState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
if (state.showCreateDmConfirmationDialog) {
|
||||||
|
val selectedUser = state.selectUsersState.selectedUsers.firstOrNull()
|
||||||
|
if (selectedUser != null) {
|
||||||
|
ConfirmationDialog(
|
||||||
|
modifier = modifier,
|
||||||
|
title = "Start chat",
|
||||||
|
content = "You're about starting a chat with ${selectedUser.getBestName()}, do you want to continue?",
|
||||||
|
submitText = stringResource(io.element.android.libraries.ui.strings.R.string._continue),
|
||||||
|
onSubmitClicked = { state.eventSink(CreateRoomRootEvents.CreateDM(selectedUser)) },
|
||||||
|
onDismiss = { state.eventSink(CreateRoomRootEvents.CancelCreateDM) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun CreateRoomRootViewLightPreview(@PreviewParameter(CreateRoomRootStateProvider::class) state: CreateRoomRootState) =
|
fun CreateRoomRootViewLightPreview(@PreviewParameter(CreateRoomRootStateProvider::class) state: CreateRoomRootState) =
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.selectusers.api.SelectUsersPresenterArgs
|
import io.element.android.features.selectusers.api.SelectUsersPresenterArgs
|
||||||
import io.element.android.features.selectusers.impl.DefaultSelectUsersPresenter
|
import io.element.android.features.selectusers.impl.DefaultSelectUsersPresenter
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -34,13 +35,15 @@ import org.junit.Test
|
||||||
class CreateRoomRootPresenterTests {
|
class CreateRoomRootPresenterTests {
|
||||||
|
|
||||||
private lateinit var presenter: CreateRoomRootPresenter
|
private lateinit var presenter: CreateRoomRootPresenter
|
||||||
|
private lateinit var fakeMatrixClient: FakeMatrixClient
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
val selectUsersPresenter = object : DefaultSelectUsersPresenter.DefaultSelectUsersFactory {
|
val selectUsersPresenter = object : DefaultSelectUsersPresenter.DefaultSelectUsersFactory {
|
||||||
override fun create(args: SelectUsersPresenterArgs) = DefaultSelectUsersPresenter(args)
|
override fun create(args: SelectUsersPresenterArgs) = DefaultSelectUsersPresenter(args)
|
||||||
}
|
}
|
||||||
presenter = CreateRoomRootPresenter(selectUsersPresenter)
|
fakeMatrixClient = FakeMatrixClient()
|
||||||
|
presenter = CreateRoomRootPresenter(selectUsersPresenter, fakeMatrixClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -64,13 +67,13 @@ class CreateRoomRootPresenterTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - trigger start DM action`() = runTest {
|
fun `present - trigger select user action`() = runTest {
|
||||||
moleculeFlow(RecompositionClock.Immediate) {
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
val matrixUser = MatrixUser(UserId("@name:matrix.org"))
|
val matrixUser = MatrixUser(UserId("@name:matrix.org"))
|
||||||
initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
initialState.eventSink(CreateRoomRootEvents.SelectUser(matrixUser))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,6 @@ sealed interface SelectUsersEvents {
|
||||||
data class UpdateSearchQuery(val query: String) : SelectUsersEvents
|
data class UpdateSearchQuery(val query: String) : SelectUsersEvents
|
||||||
data class AddToSelection(val matrixUser: MatrixUser) : SelectUsersEvents
|
data class AddToSelection(val matrixUser: MatrixUser) : SelectUsersEvents
|
||||||
data class RemoveFromSelection(val matrixUser: MatrixUser) : SelectUsersEvents
|
data class RemoveFromSelection(val matrixUser: MatrixUser) : SelectUsersEvents
|
||||||
|
object ClearSelection : SelectUsersEvents
|
||||||
data class OnSearchActiveChanged(val active: Boolean) : SelectUsersEvents
|
data class OnSearchActiveChanged(val active: Boolean) : SelectUsersEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ class DefaultSelectUsersPresenter @AssistedInject constructor(
|
||||||
localCoroutineScope.scrollToFirstSelectedUser(selectedUsersListState)
|
localCoroutineScope.scrollToFirstSelectedUser(selectedUsersListState)
|
||||||
}
|
}
|
||||||
is SelectUsersEvents.RemoveFromSelection -> selectedUsers.value = selectedUsers.value.minus(event.matrixUser).toImmutableList()
|
is SelectUsersEvents.RemoveFromSelection -> selectedUsers.value = selectedUsers.value.minus(event.matrixUser).toImmutableList()
|
||||||
|
SelectUsersEvents.ClearSelection -> selectedUsers.value = persistentListOf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.api
|
||||||
|
|
||||||
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.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
import io.element.android.libraries.matrix.api.media.MediaResolver
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
||||||
|
|
@ -28,6 +29,8 @@ interface MatrixClient : Closeable {
|
||||||
val sessionId: SessionId
|
val sessionId: SessionId
|
||||||
val roomSummaryDataSource: RoomSummaryDataSource
|
val roomSummaryDataSource: RoomSummaryDataSource
|
||||||
fun getRoom(roomId: RoomId): MatrixRoom?
|
fun getRoom(roomId: RoomId): MatrixRoom?
|
||||||
|
suspend fun createDM(userId: UserId): Result<RoomId>
|
||||||
|
fun findDM(userId: UserId): MatrixRoom?
|
||||||
fun startSync()
|
fun startSync()
|
||||||
fun stopSync()
|
fun stopSync()
|
||||||
fun mediaResolver(): MediaResolver
|
fun mediaResolver(): MediaResolver
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,10 @@ import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
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.CreateRoomParameters
|
||||||
import org.matrix.rustcomponents.sdk.RequiredState
|
import org.matrix.rustcomponents.sdk.RequiredState
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomPreset
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomVisibility
|
||||||
import org.matrix.rustcomponents.sdk.SlidingSyncListBuilder
|
import org.matrix.rustcomponents.sdk.SlidingSyncListBuilder
|
||||||
import org.matrix.rustcomponents.sdk.SlidingSyncMode
|
import org.matrix.rustcomponents.sdk.SlidingSyncMode
|
||||||
import org.matrix.rustcomponents.sdk.SlidingSyncRequestListFilters
|
import org.matrix.rustcomponents.sdk.SlidingSyncRequestListFilters
|
||||||
|
|
@ -154,6 +157,30 @@ class RustMatrixClient constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun findDM(userId: UserId): MatrixRoom? {
|
||||||
|
val roomId = client.getDmRoom(userId.value)?.use { RoomId(it.id()) }
|
||||||
|
return roomId?.let { getRoom(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun createDM(userId: UserId): Result<RoomId> =
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
runCatching {
|
||||||
|
val roomId = client.createRoom(
|
||||||
|
CreateRoomParameters(
|
||||||
|
name = "",
|
||||||
|
topic = null,
|
||||||
|
isEncrypted = true,
|
||||||
|
isDirect = true,
|
||||||
|
visibility = RoomVisibility.PRIVATE,
|
||||||
|
preset = RoomPreset.TRUSTED_PRIVATE_CHAT,
|
||||||
|
invite = listOf(userId.value),
|
||||||
|
avatar = null,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
RoomId(roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun mediaResolver(): MediaResolver = mediaResolver
|
override fun mediaResolver(): MediaResolver = mediaResolver
|
||||||
|
|
||||||
override fun sessionVerificationService(): SessionVerificationService = verificationService
|
override fun sessionVerificationService(): SessionVerificationService = verificationService
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.test
|
||||||
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.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
import io.element.android.libraries.matrix.api.media.MediaResolver
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
|
||||||
|
|
@ -37,12 +38,22 @@ class FakeMatrixClient(
|
||||||
private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService()
|
private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService()
|
||||||
) : MatrixClient {
|
) : MatrixClient {
|
||||||
|
|
||||||
|
private var createDmResult: Result<RoomId> = Result.success(A_ROOM_ID)
|
||||||
|
private var findDmResult: MatrixRoom? = FakeMatrixRoom()
|
||||||
private var logoutFailure: Throwable? = null
|
private var logoutFailure: Throwable? = null
|
||||||
|
|
||||||
override fun getRoom(roomId: RoomId): MatrixRoom? {
|
override fun getRoom(roomId: RoomId): MatrixRoom? {
|
||||||
return FakeMatrixRoom(roomId)
|
return FakeMatrixRoom(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun createDM(userId: UserId): Result<RoomId> {
|
||||||
|
return createDmResult
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findDM(userId: UserId): MatrixRoom? {
|
||||||
|
return findDmResult
|
||||||
|
}
|
||||||
|
|
||||||
override fun startSync() = Unit
|
override fun startSync() = Unit
|
||||||
|
|
||||||
override fun stopSync() = Unit
|
override fun stopSync() = Unit
|
||||||
|
|
@ -51,10 +62,6 @@ class FakeMatrixClient(
|
||||||
return FakeMediaResolver()
|
return FakeMediaResolver()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun givenLogoutError(failure: Throwable) {
|
|
||||||
logoutFailure = failure
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun logout() {
|
override suspend fun logout() {
|
||||||
delay(100)
|
delay(100)
|
||||||
logoutFailure?.let { throw it }
|
logoutFailure?.let { throw it }
|
||||||
|
|
@ -81,4 +88,18 @@ class FakeMatrixClient(
|
||||||
override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService
|
override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService
|
||||||
|
|
||||||
override fun onSlidingSyncUpdate() {}
|
override fun onSlidingSyncUpdate() {}
|
||||||
|
|
||||||
|
// Mocks
|
||||||
|
|
||||||
|
fun givenLogoutError(failure: Throwable) {
|
||||||
|
logoutFailure = failure
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenCreateDmResult(result: Result<RoomId>) {
|
||||||
|
createDmResult = result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenFindDmResult(result: MatrixRoom?) {
|
||||||
|
findDmResult = result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue