Merge branch 'develop' into feature/bma/push
This commit is contained in:
@@ -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
|
||||||
@@ -199,7 +200,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)
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ koverMerged {
|
|||||||
overrideClassFilter {
|
overrideClassFilter {
|
||||||
includes += "*Presenter"
|
includes += "*Presenter"
|
||||||
excludes += "*TemplatePresenter"
|
excludes += "*TemplatePresenter"
|
||||||
|
excludes += "*Fake*Presenter"
|
||||||
excludes += "io.element.android.appnav.loggedin.LoggedInPresenter$*"
|
excludes += "io.element.android.appnav.loggedin.LoggedInPresenter$*"
|
||||||
}
|
}
|
||||||
bound {
|
bound {
|
||||||
|
|||||||
1
changelog.d/96.feature
Normal file
1
changelog.d/96.feature
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[Create and join rooms] Show or create direct message room
|
||||||
@@ -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,7 @@ 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 StartDM(val matrixUser: MatrixUser) : CreateRoomRootEvents
|
||||||
|
object CancelStartDM : 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,19 +17,29 @@
|
|||||||
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 io.element.android.features.userlist.api.SelectionMode
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import io.element.android.features.userlist.api.MatrixUserDataSource
|
import io.element.android.features.userlist.api.MatrixUserDataSource
|
||||||
|
import io.element.android.features.userlist.api.SelectionMode
|
||||||
import io.element.android.features.userlist.api.UserListPresenter
|
import io.element.android.features.userlist.api.UserListPresenter
|
||||||
import io.element.android.features.userlist.api.UserListPresenterArgs
|
import io.element.android.features.userlist.api.UserListPresenterArgs
|
||||||
|
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
|
||||||
import javax.inject.Named
|
import javax.inject.Named
|
||||||
|
|
||||||
class CreateRoomRootPresenter @Inject constructor(
|
class CreateRoomRootPresenter @Inject constructor(
|
||||||
private val presenterFactory: UserListPresenter.Factory,
|
private val presenterFactory: UserListPresenter.Factory,
|
||||||
@Named("AllUsers") private val matrixUserDataSource: MatrixUserDataSource,
|
@Named("AllUsers") private val matrixUserDataSource: MatrixUserDataSource,
|
||||||
|
private val matrixClient: MatrixClient,
|
||||||
) : Presenter<CreateRoomRootState> {
|
) : Presenter<CreateRoomRootState> {
|
||||||
|
|
||||||
private val presenter by lazy {
|
private val presenter by lazy {
|
||||||
@@ -43,20 +53,37 @@ class CreateRoomRootPresenter @Inject constructor(
|
|||||||
override fun present(): CreateRoomRootState {
|
override fun present(): CreateRoomRootState {
|
||||||
val userListState = presenter.present()
|
val userListState = presenter.present()
|
||||||
|
|
||||||
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
val startDmAction: MutableState<Async<RoomId>> = remember { mutableStateOf(Async.Uninitialized) }
|
||||||
|
|
||||||
|
fun startDm(matrixUser: MatrixUser) {
|
||||||
|
startDmAction.value = Async.Uninitialized
|
||||||
|
val existingDM = matrixClient.findDM(matrixUser.id)
|
||||||
|
if (existingDM == null) {
|
||||||
|
localCoroutineScope.createDM(matrixUser, startDmAction)
|
||||||
|
} else {
|
||||||
|
startDmAction.value = Async.Success(existingDM.roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun handleEvents(event: CreateRoomRootEvents) {
|
fun handleEvents(event: CreateRoomRootEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is CreateRoomRootEvents.StartDM -> handleStartDM(event.matrixUser)
|
is CreateRoomRootEvents.StartDM -> startDm(event.matrixUser)
|
||||||
|
CreateRoomRootEvents.CancelStartDM -> startDmAction.value = Async.Uninitialized
|
||||||
CreateRoomRootEvents.InvitePeople -> Unit // Todo Handle invite people action
|
CreateRoomRootEvents.InvitePeople -> Unit // Todo Handle invite people action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreateRoomRootState(
|
return CreateRoomRootState(
|
||||||
userListState = userListState,
|
userListState = userListState,
|
||||||
|
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,11 @@
|
|||||||
package io.element.android.features.createroom.impl.root
|
package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
import io.element.android.features.userlist.api.UserListState
|
import io.element.android.features.userlist.api.UserListState
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
data class CreateRoomRootState(
|
data class CreateRoomRootState(
|
||||||
val userListState: UserListState,
|
val userListState: UserListState,
|
||||||
|
val startDmAction: Async<RoomId>,
|
||||||
val eventSink: (CreateRoomRootEvents) -> Unit,
|
val eventSink: (CreateRoomRootEvents) -> Unit,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,15 +18,41 @@ 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.userlist.api.aUserListState
|
import io.element.android.features.userlist.api.aUserListState
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
|
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRootState> {
|
open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRootState> {
|
||||||
override val values: Sequence<CreateRoomRootState>
|
override val values: Sequence<CreateRoomRootState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aCreateRoomRootState(),
|
aCreateRoomRootState(),
|
||||||
|
aCreateRoomRootState().copy(
|
||||||
|
startDmAction = Async.Loading(),
|
||||||
|
userListState = aMatrixUser().let {
|
||||||
|
aUserListState().copy(
|
||||||
|
searchQuery = it.id.value,
|
||||||
|
searchResults = persistentListOf(it),
|
||||||
|
selectedUsers = persistentListOf(it),
|
||||||
|
isSearchActive = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
aCreateRoomRootState().copy(
|
||||||
|
startDmAction = Async.Failure(Throwable()),
|
||||||
|
userListState = aMatrixUser().let {
|
||||||
|
aUserListState().copy(
|
||||||
|
searchQuery = it.id.value,
|
||||||
|
searchResults = persistentListOf(it),
|
||||||
|
selectedUsers = persistentListOf(it),
|
||||||
|
isSearchActive = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aCreateRoomRootState() = CreateRoomRootState(
|
fun aCreateRoomRootState() = CreateRoomRootState(
|
||||||
eventSink = {},
|
eventSink = {},
|
||||||
userListState = aUserListState(),
|
startDmAction = Async.Uninitialized,
|
||||||
|
userListState = aUserListState(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package io.element.android.features.createroom.impl.root
|
package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -28,17 +29,22 @@ 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
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import io.element.android.features.userlist.api.UserListView
|
|
||||||
import io.element.android.features.createroom.impl.R
|
import io.element.android.features.createroom.impl.R
|
||||||
|
import io.element.android.features.userlist.api.UserListView
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
|
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
|
||||||
@@ -46,6 +52,7 @@ 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.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 +63,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 = {
|
||||||
@@ -69,10 +83,16 @@ fun CreateRoomRootView(
|
|||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
UserListView(
|
UserListView(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
state = state.userListState,
|
state = state.userListState,
|
||||||
onUserSelected = { state.eventSink.invoke(CreateRoomRootEvents.StartDM(it)) },
|
onUserSelected = {
|
||||||
|
// Fixme disabled DM creation since it can break the account data which is not correctly synced
|
||||||
|
// uncomment to enable it again or move behind a feature flag
|
||||||
|
Toast.makeText(context, "Create DM feature is disabled.", Toast.LENGTH_SHORT).show()
|
||||||
|
// state.eventSink(CreateRoomRootEvents.StartDM(it))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!state.userListState.isSearchActive) {
|
if (!state.userListState.isSearchActive) {
|
||||||
@@ -83,6 +103,25 @@ fun CreateRoomRootView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when (state.startDmAction) {
|
||||||
|
is Async.Loading -> {
|
||||||
|
ProgressDialog(text = stringResource(id = StringR.string.common_creating_room))
|
||||||
|
}
|
||||||
|
is Async.Failure -> {
|
||||||
|
RetryDialog(
|
||||||
|
content = stringResource(id = StringR.string.screen_start_chat_error_starting_chat),
|
||||||
|
onDismiss = { state.eventSink(CreateRoomRootEvents.CancelStartDM) },
|
||||||
|
onRetry = {
|
||||||
|
state.userListState.selectedUsers.firstOrNull()
|
||||||
|
?.let { state.eventSink(CreateRoomRootEvents.StartDM(it)) }
|
||||||
|
// Cancel start DM if there is no more selected user (should not happen)
|
||||||
|
?: state.eventSink(CreateRoomRootEvents.CancelStartDM)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|||||||
@@ -22,10 +22,8 @@ import app.cash.molecule.RecompositionClock
|
|||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.userlist.api.MatrixUserDataSource
|
|
||||||
import io.element.android.features.userlist.api.UserListPresenterArgs
|
|
||||||
import io.element.android.features.userlist.impl.DefaultUserListPresenter
|
|
||||||
import io.element.android.features.userlist.test.FakeMatrixUserDataSource
|
import io.element.android.features.userlist.test.FakeMatrixUserDataSource
|
||||||
|
import io.element.android.features.userlist.test.FakeUserListPresenterFactory
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -37,11 +35,7 @@ class AddPeoplePresenterTests {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
val userListFactory = object : DefaultUserListPresenter.DefaultUserListFactory {
|
presenter = AddPeoplePresenter(FakeUserListPresenterFactory(), FakeMatrixUserDataSource())
|
||||||
override fun create(args: UserListPresenterArgs, dataSource: MatrixUserDataSource) = DefaultUserListPresenter(args, dataSource)
|
|
||||||
}
|
|
||||||
val dataSource = FakeMatrixUserDataSource()
|
|
||||||
presenter = AddPeoplePresenter(userListFactory, dataSource)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -22,12 +22,18 @@ import app.cash.molecule.RecompositionClock
|
|||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.userlist.api.MatrixUserDataSource
|
import io.element.android.features.userlist.api.aUserListState
|
||||||
import io.element.android.features.userlist.api.UserListPresenterArgs
|
|
||||||
import io.element.android.features.userlist.impl.DefaultUserListPresenter
|
|
||||||
import io.element.android.features.userlist.test.FakeMatrixUserDataSource
|
import io.element.android.features.userlist.test.FakeMatrixUserDataSource
|
||||||
|
import io.element.android.features.userlist.test.FakeUserListPresenter
|
||||||
|
import io.element.android.features.userlist.test.FakeUserListPresenterFactory
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -37,14 +43,15 @@ class CreateRoomRootPresenterTests {
|
|||||||
|
|
||||||
private lateinit var userListDataSource: FakeMatrixUserDataSource
|
private lateinit var userListDataSource: FakeMatrixUserDataSource
|
||||||
private lateinit var presenter: CreateRoomRootPresenter
|
private lateinit var presenter: CreateRoomRootPresenter
|
||||||
|
private lateinit var fakeUserListPresenter: FakeUserListPresenter
|
||||||
|
private lateinit var fakeMatrixClient: FakeMatrixClient
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
val userListPresenter = object : DefaultUserListPresenter.DefaultUserListFactory {
|
fakeUserListPresenter = FakeUserListPresenter()
|
||||||
override fun create(args: UserListPresenterArgs, dataSource: MatrixUserDataSource) = DefaultUserListPresenter(args, dataSource)
|
fakeMatrixClient = FakeMatrixClient()
|
||||||
}
|
|
||||||
userListDataSource = FakeMatrixUserDataSource()
|
userListDataSource = FakeMatrixUserDataSource()
|
||||||
presenter = CreateRoomRootPresenter(userListPresenter, userListDataSource)
|
presenter = CreateRoomRootPresenter(FakeUserListPresenterFactory(fakeUserListPresenter), userListDataSource, fakeMatrixClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -68,13 +75,82 @@ class CreateRoomRootPresenterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - trigger start DM action`() = runTest {
|
fun `present - trigger create DM 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"))
|
||||||
|
val createDmResult = Result.success(RoomId("!createDmResult"))
|
||||||
|
|
||||||
|
fakeMatrixClient.givenFindDmResult(null)
|
||||||
|
fakeMatrixClient.givenCreateDmResult(createDmResult)
|
||||||
|
|
||||||
initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
||||||
|
assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java)
|
||||||
|
val stateAfterStartDM = awaitItem()
|
||||||
|
assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Success::class.java)
|
||||||
|
assertThat(stateAfterStartDM.startDmAction.dataOrNull()).isEqualTo(createDmResult.getOrNull())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - trigger retrieve DM action`() = runTest {
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
val matrixUser = MatrixUser(UserId("@name:matrix.org"))
|
||||||
|
val fakeDmResult = FakeMatrixRoom(RoomId("!fakeDmResult"))
|
||||||
|
|
||||||
|
fakeMatrixClient.givenFindDmResult(fakeDmResult)
|
||||||
|
|
||||||
|
initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
||||||
|
val stateAfterStartDM = awaitItem()
|
||||||
|
assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Success::class.java)
|
||||||
|
assertThat(stateAfterStartDM.startDmAction.dataOrNull()).isEqualTo(fakeDmResult.roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - trigger retry create DM action`() = runTest {
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
val matrixUser = MatrixUser(UserId("@name:matrix.org"))
|
||||||
|
val createDmResult = Result.success(RoomId("!createDmResult"))
|
||||||
|
fakeUserListPresenter.givenState(aUserListState().copy(selectedUsers = persistentListOf(matrixUser)))
|
||||||
|
|
||||||
|
fakeMatrixClient.givenFindDmResult(null)
|
||||||
|
fakeMatrixClient.givenCreateDmError(A_THROWABLE)
|
||||||
|
fakeMatrixClient.givenCreateDmResult(createDmResult)
|
||||||
|
|
||||||
|
// Failure
|
||||||
|
initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
||||||
|
assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java)
|
||||||
|
val stateAfterStartDM = awaitItem()
|
||||||
|
assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Failure::class.java)
|
||||||
|
|
||||||
|
// Cancel
|
||||||
|
stateAfterStartDM.eventSink(CreateRoomRootEvents.CancelStartDM)
|
||||||
|
val stateAfterCancel = awaitItem()
|
||||||
|
assertThat(stateAfterCancel.startDmAction).isInstanceOf(Async.Uninitialized::class.java)
|
||||||
|
|
||||||
|
// Failure
|
||||||
|
stateAfterCancel.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
||||||
|
assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java)
|
||||||
|
val stateAfterSecondAttempt = awaitItem()
|
||||||
|
assertThat(stateAfterSecondAttempt.startDmAction).isInstanceOf(Async.Failure::class.java)
|
||||||
|
|
||||||
|
// Retry with success
|
||||||
|
fakeMatrixClient.givenCreateDmError(null)
|
||||||
|
stateAfterSecondAttempt.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
||||||
|
assertThat(awaitItem().startDmAction).isInstanceOf(Async.Uninitialized::class.java)
|
||||||
|
assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java)
|
||||||
|
val stateAfterRetryStartDM = awaitItem()
|
||||||
|
assertThat(stateAfterRetryStartDM.startDmAction).isInstanceOf(Async.Success::class.java)
|
||||||
|
assertThat(stateAfterRetryStartDM.startDmAction.dataOrNull()).isEqualTo(createDmResult.getOrNull())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
|
|
||||||
@Suppress("DSL_SCOPE_VIOLATION")
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-library")
|
id("io.element.android-compose-library")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -25,6 +23,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrixui)
|
implementation(projects.libraries.matrixui)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
api(projects.features.userlist.api)
|
api(projects.features.userlist.api)
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.userlist.test
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import io.element.android.features.userlist.api.UserListPresenter
|
||||||
|
import io.element.android.features.userlist.api.UserListState
|
||||||
|
import io.element.android.features.userlist.api.aUserListState
|
||||||
|
|
||||||
|
class FakeUserListPresenter : UserListPresenter {
|
||||||
|
|
||||||
|
private var state = aUserListState()
|
||||||
|
|
||||||
|
fun givenState(state: UserListState) {
|
||||||
|
this.state = state
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun present(): UserListState {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.userlist.test
|
||||||
|
|
||||||
|
import io.element.android.features.userlist.api.MatrixUserDataSource
|
||||||
|
import io.element.android.features.userlist.api.UserListPresenter
|
||||||
|
import io.element.android.features.userlist.api.UserListPresenterArgs
|
||||||
|
|
||||||
|
class FakeUserListPresenterFactory(
|
||||||
|
private val fakeUserListPresenter: FakeUserListPresenter = FakeUserListPresenter()
|
||||||
|
) : UserListPresenter.Factory {
|
||||||
|
|
||||||
|
override fun create(args: UserListPresenterArgs, matrixUserDataSource: MatrixUserDataSource): UserListPresenter = fakeUserListPresenter
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.designsystem.components.dialogs
|
||||||
|
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
import io.element.android.libraries.ui.strings.R as StringR
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RetryDialog(
|
||||||
|
content: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
title: String = RetryDialogDefaults.title,
|
||||||
|
retryText: String = RetryDialogDefaults.retryText,
|
||||||
|
dismissText: String = RetryDialogDefaults.dismissText,
|
||||||
|
onRetry: () -> Unit = {},
|
||||||
|
onDismiss: () -> Unit = {},
|
||||||
|
shape: Shape = AlertDialogDefaults.shape,
|
||||||
|
containerColor: Color = AlertDialogDefaults.containerColor,
|
||||||
|
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
|
||||||
|
titleContentColor: Color = AlertDialogDefaults.titleContentColor,
|
||||||
|
textContentColor: Color = AlertDialogDefaults.textContentColor,
|
||||||
|
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
modifier = modifier,
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = {
|
||||||
|
Text(title)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(content)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onRetry) {
|
||||||
|
Text(retryText)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(dismissText)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shape = shape,
|
||||||
|
containerColor = containerColor,
|
||||||
|
iconContentColor = iconContentColor,
|
||||||
|
titleContentColor = titleContentColor,
|
||||||
|
textContentColor = textContentColor,
|
||||||
|
tonalElevation = tonalElevation,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object RetryDialogDefaults {
|
||||||
|
val title: String @Composable get() = stringResource(id = StringR.string.dialog_title_error)
|
||||||
|
val retryText: String @Composable get() = stringResource(id = StringR.string.action_retry)
|
||||||
|
val dismissText: String @Composable get() = stringResource(id = StringR.string.action_cancel)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
internal fun RetryDialogLightPreview() = ElementPreviewLight { ContentToPreview() }
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
internal fun RetryDialogDarkPreview() = ElementPreviewDark { ContentToPreview() }
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ContentToPreview() {
|
||||||
|
RetryDialog(
|
||||||
|
content = "Content",
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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.notification.NotificationService
|
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||||
@@ -31,6 +32,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
|
||||||
|
|||||||
@@ -42,7 +42,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
|
||||||
@@ -167,6 +170,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 = null,
|
||||||
|
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.notification.NotificationService
|
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||||
@@ -44,12 +45,25 @@ class FakeMatrixClient(
|
|||||||
private val notificationService: FakeNotificationService = FakeNotificationService(),
|
private val notificationService: FakeNotificationService = FakeNotificationService(),
|
||||||
) : MatrixClient {
|
) : MatrixClient {
|
||||||
|
|
||||||
|
private var createDmResult: Result<RoomId> = Result.success(A_ROOM_ID)
|
||||||
|
private var createDmFailure: Throwable? = null
|
||||||
|
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> {
|
||||||
|
delay(100)
|
||||||
|
createDmFailure?.let { throw it }
|
||||||
|
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
|
||||||
@@ -58,10 +72,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 }
|
||||||
@@ -96,4 +106,22 @@ class FakeMatrixClient(
|
|||||||
override fun roomMembershipObserver(): RoomMembershipObserver {
|
override fun roomMembershipObserver(): RoomMembershipObserver {
|
||||||
return RoomMembershipObserver(A_SESSION_ID)
|
return RoomMembershipObserver(A_SESSION_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mocks
|
||||||
|
|
||||||
|
fun givenLogoutError(failure: Throwable?) {
|
||||||
|
logoutFailure = failure
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenCreateDmResult(result: Result<RoomId>) {
|
||||||
|
createDmResult = result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenCreateDmError(failure: Throwable?) {
|
||||||
|
createDmFailure = failure
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenFindDmResult(result: MatrixRoom?) {
|
||||||
|
findDmResult = result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
configurations.maybeCreate("default")
|
|
||||||
artifacts.add("default", file('matrix-rust-sdk.aar'))
|
|
||||||
2
libraries/rustsdk/build.gradle.kts
Normal file
2
libraries/rustsdk/build.gradle.kts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
configurations.maybeCreate("default")
|
||||||
|
artifacts.add("default", file("matrix-rust-sdk.aar"))
|
||||||
@@ -51,7 +51,6 @@ include(":appnav")
|
|||||||
include(":tests:uitests")
|
include(":tests:uitests")
|
||||||
include(":anvilannotations")
|
include(":anvilannotations")
|
||||||
include(":anvilcodegen")
|
include(":anvilcodegen")
|
||||||
include(":libraries:rustsdk")
|
|
||||||
|
|
||||||
include(":samples:minimal")
|
include(":samples:minimal")
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user