Accepting and declining invites
Hook up accept and decline buttons in the invites UI. Accept will attempt to accept and then navigate to the room; decline shows a confirmation dialog. Fixes #106
This commit is contained in:
parent
5e33784d38
commit
ae97a034e5
26 changed files with 582 additions and 77 deletions
|
|
@ -227,6 +227,10 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||||
override fun onBackClicked() {
|
override fun onBackClicked() {
|
||||||
backstack.pop()
|
backstack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onInviteAccepted(roomId: RoomId) {
|
||||||
|
backstack.push(NavTarget.Room(roomId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inviteListEntryPoint.nodeBuilder(this, buildContext)
|
inviteListEntryPoint.nodeBuilder(this, buildContext)
|
||||||
|
|
|
||||||
1
changelog.d/106.feature
Normal file
1
changelog.d/106.feature
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
[Create and join rooms] Accept or decline an invite from invitation list
|
||||||
|
|
@ -24,4 +24,5 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
|
implementation(projects.libraries.matrix.api)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ 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 io.element.android.libraries.architecture.FeatureEntryPoint
|
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
interface InviteListEntryPoint : FeatureEntryPoint {
|
interface InviteListEntryPoint : FeatureEntryPoint {
|
||||||
|
|
||||||
|
|
@ -32,6 +33,8 @@ interface InviteListEntryPoint : FeatureEntryPoint {
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onBackClicked()
|
fun onBackClicked()
|
||||||
|
|
||||||
|
fun onInviteAccepted(roomId: RoomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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.invitelist.impl
|
||||||
|
|
||||||
|
import io.element.android.features.invitelist.impl.model.InviteListInviteSummary
|
||||||
|
|
||||||
|
sealed interface InviteListEvents {
|
||||||
|
|
||||||
|
data class AcceptInvite(val invite: InviteListInviteSummary) : InviteListEvents
|
||||||
|
data class DeclineInvite(val invite: InviteListInviteSummary) : InviteListEvents
|
||||||
|
|
||||||
|
object ConfirmDeclineInvite: InviteListEvents
|
||||||
|
object CancelDeclineInvite: InviteListEvents
|
||||||
|
|
||||||
|
object DismissAcceptError: InviteListEvents
|
||||||
|
object DismissDeclineError: InviteListEvents
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,7 @@ import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.features.invitelist.api.InviteListEntryPoint
|
import io.element.android.features.invitelist.api.InviteListEntryPoint
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
class InviteListNode @AssistedInject constructor(
|
class InviteListNode @AssistedInject constructor(
|
||||||
|
|
@ -39,12 +40,17 @@ class InviteListNode @AssistedInject constructor(
|
||||||
plugins<InviteListEntryPoint.Callback>().forEach { it.onBackClicked() }
|
plugins<InviteListEntryPoint.Callback>().forEach { it.onBackClicked() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onInviteAccepted(roomId: RoomId) {
|
||||||
|
plugins<InviteListEntryPoint.Callback>().forEach { it.onInviteAccepted(roomId) }
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
InviteListView(
|
InviteListView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackClicked = ::onBackClicked,
|
onBackClicked = ::onBackClicked,
|
||||||
|
onInviteAccepted = ::onInviteAccepted,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,24 @@
|
||||||
package io.element.android.features.invitelist.impl
|
package io.element.android.features.invitelist.impl
|
||||||
|
|
||||||
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.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import io.element.android.features.invitelist.impl.model.InviteListInviteSummary
|
import io.element.android.features.invitelist.impl.model.InviteListInviteSummary
|
||||||
import io.element.android.features.invitelist.impl.model.InviteSender
|
import io.element.android.features.invitelist.impl.model.InviteSender
|
||||||
|
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.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
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.room.RoomSummary
|
import io.element.android.libraries.matrix.api.room.RoomSummary
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class InviteListPresenter @Inject constructor(
|
class InviteListPresenter @Inject constructor(
|
||||||
|
|
@ -39,11 +48,71 @@ class InviteListPresenter @Inject constructor(
|
||||||
.roomSummaries()
|
.roomSummaries()
|
||||||
.collectAsState()
|
.collectAsState()
|
||||||
|
|
||||||
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
val acceptedAction: MutableState<Async<RoomId>> = remember { mutableStateOf(Async.Uninitialized) }
|
||||||
|
val declinedAction: MutableState<Async<Unit>> = remember { mutableStateOf(Async.Uninitialized) }
|
||||||
|
val decliningInvite: MutableState<InviteListInviteSummary?> = remember { mutableStateOf(null) }
|
||||||
|
|
||||||
|
fun handleEvent(event: InviteListEvents) {
|
||||||
|
when (event) {
|
||||||
|
is InviteListEvents.AcceptInvite -> {
|
||||||
|
acceptedAction.value = Async.Uninitialized
|
||||||
|
localCoroutineScope.acceptInvite(event.invite.roomId, acceptedAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
is InviteListEvents.DeclineInvite -> {
|
||||||
|
decliningInvite.value = event.invite
|
||||||
|
}
|
||||||
|
|
||||||
|
is InviteListEvents.ConfirmDeclineInvite -> {
|
||||||
|
declinedAction.value = Async.Uninitialized
|
||||||
|
decliningInvite.value?.let {
|
||||||
|
localCoroutineScope.declineInvite(it.roomId, declinedAction)
|
||||||
|
}
|
||||||
|
decliningInvite.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
is InviteListEvents.CancelDeclineInvite -> {
|
||||||
|
decliningInvite.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
is InviteListEvents.DismissAcceptError -> {
|
||||||
|
acceptedAction.value = Async.Uninitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
is InviteListEvents.DismissDeclineError -> {
|
||||||
|
declinedAction.value = Async.Uninitialized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return InviteListState(
|
return InviteListState(
|
||||||
inviteList = invites.mapNotNull(::toInviteSummary).toPersistentList(),
|
inviteList = invites.mapNotNull(::toInviteSummary).toPersistentList(),
|
||||||
|
declineConfirmationDialog = decliningInvite.value?.let {
|
||||||
|
InviteDeclineConfirmationDialog.Visible(
|
||||||
|
isDirect = it.isDirect,
|
||||||
|
name = it.roomName,
|
||||||
|
)
|
||||||
|
} ?: InviteDeclineConfirmationDialog.Hidden,
|
||||||
|
acceptedAction = acceptedAction.value,
|
||||||
|
declinedAction = declinedAction.value,
|
||||||
|
eventSink = ::handleEvent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.acceptInvite(roomId: RoomId, acceptedAction: MutableState<Async<RoomId>>) = launch {
|
||||||
|
suspend {
|
||||||
|
client.getRoom(roomId)?.acceptInvitation()?.getOrThrow()
|
||||||
|
roomId
|
||||||
|
}.execute(acceptedAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.declineInvite(roomId: RoomId, declinedAction: MutableState<Async<Unit>>) = launch {
|
||||||
|
suspend {
|
||||||
|
client.getRoom(roomId)?.rejectInvitation()?.getOrThrow() ?: Unit
|
||||||
|
}.execute(declinedAction)
|
||||||
|
}
|
||||||
|
|
||||||
private fun toInviteSummary(roomSummary: RoomSummary): InviteListInviteSummary? {
|
private fun toInviteSummary(roomSummary: RoomSummary): InviteListInviteSummary? {
|
||||||
return when (roomSummary) {
|
return when (roomSummary) {
|
||||||
is RoomSummary.Filled -> roomSummary.details.run {
|
is RoomSummary.Filled -> roomSummary.details.run {
|
||||||
|
|
@ -71,6 +140,7 @@ class InviteListPresenter @Inject constructor(
|
||||||
roomName = name,
|
roomName = name,
|
||||||
roomAlias = alias,
|
roomAlias = alias,
|
||||||
roomAvatarData = avatarData,
|
roomAvatarData = avatarData,
|
||||||
|
isDirect = isDirect,
|
||||||
sender = if (isDirect) null else inviter?.let {
|
sender = if (isDirect) null else inviter?.let {
|
||||||
InviteSender(
|
InviteSender(
|
||||||
userId = it.userId,
|
userId = it.userId,
|
||||||
|
|
@ -81,9 +151,10 @@ class InviteListPresenter @Inject constructor(
|
||||||
url = it.avatarUrl,
|
url = it.avatarUrl,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,20 @@ package io.element.android.features.invitelist.impl
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.features.invitelist.impl.model.InviteListInviteSummary
|
import io.element.android.features.invitelist.impl.model.InviteListInviteSummary
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class InviteListState(
|
data class InviteListState(
|
||||||
val inviteList: ImmutableList<InviteListInviteSummary>
|
val inviteList: ImmutableList<InviteListInviteSummary>,
|
||||||
|
val declineConfirmationDialog: InviteDeclineConfirmationDialog = InviteDeclineConfirmationDialog.Hidden,
|
||||||
|
val acceptedAction: Async<RoomId> = Async.Uninitialized,
|
||||||
|
val declinedAction: Async<Unit> = Async.Uninitialized,
|
||||||
|
val eventSink: (InviteListEvents) -> Unit = {}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
sealed interface InviteDeclineConfirmationDialog {
|
||||||
|
object Hidden : InviteDeclineConfirmationDialog
|
||||||
|
data class Visible(val isDirect: Boolean, val name: String) : InviteDeclineConfirmationDialog
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.invitelist.impl
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.invitelist.impl.model.InviteListInviteSummary
|
import io.element.android.features.invitelist.impl.model.InviteListInviteSummary
|
||||||
import io.element.android.features.invitelist.impl.model.InviteSender
|
import io.element.android.features.invitelist.impl.model.InviteSender
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
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 kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
@ -28,7 +29,11 @@ open class InviteListStateProvider : PreviewParameterProvider<InviteListState> {
|
||||||
override val values: Sequence<InviteListState>
|
override val values: Sequence<InviteListState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aInviteListState(),
|
aInviteListState(),
|
||||||
aInviteListState().copy(inviteList = persistentListOf())
|
aInviteListState().copy(inviteList = persistentListOf()),
|
||||||
|
aInviteListState().copy(declineConfirmationDialog = InviteDeclineConfirmationDialog.Visible(true, "Alice")),
|
||||||
|
aInviteListState().copy(declineConfirmationDialog = InviteDeclineConfirmationDialog.Visible(false, "Some Room")),
|
||||||
|
aInviteListState().copy(acceptedAction = Async.Failure(Throwable("Whoops"))),
|
||||||
|
aInviteListState().copy(declinedAction = Async.Failure(Throwable("Whoops"))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
|
@ -33,7 +34,10 @@ 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 io.element.android.features.invitelist.impl.components.InviteSummaryRow
|
import io.element.android.features.invitelist.impl.components.InviteSummaryRow
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
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.ConfirmationDialog
|
||||||
|
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||||
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.Scaffold
|
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
|
|
@ -47,16 +51,56 @@ fun InviteListView(
|
||||||
state: InviteListState,
|
state: InviteListState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onBackClicked: () -> Unit = {},
|
onBackClicked: () -> Unit = {},
|
||||||
onAcceptClicked: (RoomId) -> Unit = {},
|
onInviteAccepted: (RoomId) -> Unit = {},
|
||||||
onDeclineClicked: (RoomId) -> Unit = {},
|
|
||||||
) {
|
) {
|
||||||
|
if (state.acceptedAction is Async.Success) {
|
||||||
|
LaunchedEffect(state.acceptedAction) {
|
||||||
|
onInviteAccepted(state.acceptedAction.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
InviteListContent(
|
InviteListContent(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onBackClicked = onBackClicked,
|
onBackClicked = onBackClicked,
|
||||||
onAcceptClicked = onAcceptClicked,
|
|
||||||
onDeclineClicked = onDeclineClicked,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (state.declineConfirmationDialog is InviteDeclineConfirmationDialog.Visible) {
|
||||||
|
val contentResource = if (state.declineConfirmationDialog.isDirect)
|
||||||
|
R.string.screen_invites_decline_direct_chat_message
|
||||||
|
else
|
||||||
|
R.string.screen_invites_decline_chat_message
|
||||||
|
|
||||||
|
val titleResource = if (state.declineConfirmationDialog.isDirect)
|
||||||
|
R.string.screen_invites_decline_direct_chat_title
|
||||||
|
else
|
||||||
|
R.string.screen_invites_decline_chat_title
|
||||||
|
|
||||||
|
ConfirmationDialog(
|
||||||
|
content = stringResource(contentResource, state.declineConfirmationDialog.name),
|
||||||
|
title = stringResource(titleResource),
|
||||||
|
onSubmitClicked = { state.eventSink(InviteListEvents.ConfirmDeclineInvite) },
|
||||||
|
onDismiss = { state.eventSink(InviteListEvents.CancelDeclineInvite) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.acceptedAction is Async.Failure) {
|
||||||
|
ErrorDialog(
|
||||||
|
content = stringResource(StringR.string.error_unknown),
|
||||||
|
title = stringResource(StringR.string.common_error),
|
||||||
|
submitText = stringResource(StringR.string.action_ok),
|
||||||
|
onDismiss = { state.eventSink(InviteListEvents.DismissAcceptError) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.declinedAction is Async.Failure) {
|
||||||
|
ErrorDialog(
|
||||||
|
content = stringResource(StringR.string.error_unknown),
|
||||||
|
title = stringResource(StringR.string.common_error),
|
||||||
|
submitText = stringResource(StringR.string.action_ok),
|
||||||
|
onDismiss = { state.eventSink(InviteListEvents.DismissDeclineError) }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
@ -65,8 +109,6 @@ fun InviteListContent(
|
||||||
state: InviteListState,
|
state: InviteListState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onBackClicked: () -> Unit = {},
|
onBackClicked: () -> Unit = {},
|
||||||
onAcceptClicked: (RoomId) -> Unit = {},
|
|
||||||
onDeclineClicked: (RoomId) -> Unit = {},
|
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
@ -102,8 +144,8 @@ fun InviteListContent(
|
||||||
) { invite ->
|
) { invite ->
|
||||||
InviteSummaryRow(
|
InviteSummaryRow(
|
||||||
invite = invite,
|
invite = invite,
|
||||||
onAcceptClicked = onAcceptClicked,
|
onAcceptClicked = { state.eventSink(InviteListEvents.AcceptInvite(invite)) },
|
||||||
onDeclineClicked = onDeclineClicked,
|
onDeclineClicked = { state.eventSink(InviteListEvents.DeclineInvite(invite)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,6 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||||
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 kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import io.element.android.libraries.ui.strings.R as StringR
|
import io.element.android.libraries.ui.strings.R as StringR
|
||||||
|
|
||||||
|
|
@ -69,8 +68,8 @@ private val minHeight = 72.dp
|
||||||
internal fun InviteSummaryRow(
|
internal fun InviteSummaryRow(
|
||||||
invite: InviteListInviteSummary,
|
invite: InviteListInviteSummary,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onAcceptClicked: (RoomId) -> Unit = {},
|
onAcceptClicked: () -> Unit = {},
|
||||||
onDeclineClicked: (RoomId) -> Unit = {},
|
onDeclineClicked: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|
@ -88,8 +87,8 @@ internal fun InviteSummaryRow(
|
||||||
@Composable
|
@Composable
|
||||||
internal fun DefaultInviteSummaryRow(
|
internal fun DefaultInviteSummaryRow(
|
||||||
invite: InviteListInviteSummary,
|
invite: InviteListInviteSummary,
|
||||||
onAcceptClicked: (RoomId) -> Unit = {},
|
onAcceptClicked: () -> Unit = {},
|
||||||
onDeclineClicked: (RoomId) -> Unit = {},
|
onDeclineClicked: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -138,7 +137,7 @@ internal fun DefaultInviteSummaryRow(
|
||||||
Row(Modifier.padding(top = 12.dp)) {
|
Row(Modifier.padding(top = 12.dp)) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
content = { Text(stringResource(StringR.string.action_decline), style = ElementTextStyles.Button) },
|
content = { Text(stringResource(StringR.string.action_decline), style = ElementTextStyles.Button) },
|
||||||
onClick = { onDeclineClicked(invite.roomId) },
|
onClick = onDeclineClicked,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 7.dp),
|
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 7.dp),
|
||||||
)
|
)
|
||||||
|
|
@ -147,7 +146,7 @@ internal fun DefaultInviteSummaryRow(
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
content = { Text(stringResource(StringR.string.action_accept), style = ElementTextStyles.Button) },
|
content = { Text(stringResource(StringR.string.action_accept), style = ElementTextStyles.Button) },
|
||||||
onClick = { onAcceptClicked(invite.roomId) },
|
onClick = onAcceptClicked,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 7.dp),
|
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 7.dp),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,8 @@ data class InviteListInviteSummary(
|
||||||
val roomName: String = "",
|
val roomName: String = "",
|
||||||
val roomAlias: String? = null,
|
val roomAlias: String? = null,
|
||||||
val roomAvatarData: AvatarData = AvatarData(roomId.value, roomName),
|
val roomAvatarData: AvatarData = AvatarData(roomId.value, roomName),
|
||||||
val sender: InviteSender? = null
|
val sender: InviteSender? = null,
|
||||||
|
val isDirect: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
data class InviteSender(
|
data class InviteSender(
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ 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
|
import com.google.common.truth.Truth
|
||||||
|
import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
import io.element.android.libraries.matrix.api.room.RoomSummary
|
import io.element.android.libraries.matrix.api.room.RoomSummary
|
||||||
|
|
@ -32,6 +34,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||||
import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource
|
import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -80,32 +83,7 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - uses user ID and avatar for direct invites`() = runTest {
|
fun `present - uses user ID and avatar for direct invites`() = runTest {
|
||||||
val invitesDataSource = FakeRoomSummaryDataSource()
|
val invitesDataSource = FakeRoomSummaryDataSource().withDirectChatInvitation()
|
||||||
invitesDataSource.postRoomSummary(
|
|
||||||
listOf(
|
|
||||||
RoomSummary.Filled(
|
|
||||||
RoomSummaryDetails(
|
|
||||||
roomId = A_ROOM_ID,
|
|
||||||
name = A_USER_NAME,
|
|
||||||
avatarURLString = null,
|
|
||||||
isDirect = true,
|
|
||||||
lastMessage = null,
|
|
||||||
lastMessageTimestamp = null,
|
|
||||||
unreadNotificationCount = 0,
|
|
||||||
inviter = RoomMember(
|
|
||||||
userId = A_USER_ID,
|
|
||||||
displayName = A_USER_NAME,
|
|
||||||
avatarUrl = AN_AVATAR_URL,
|
|
||||||
membership = RoomMembershipState.JOIN,
|
|
||||||
isNameAmbiguous = false,
|
|
||||||
powerLevel = 0,
|
|
||||||
normalizedPowerLevel = 0,
|
|
||||||
isIgnored = false,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val presenter = InviteListPresenter(
|
val presenter = InviteListPresenter(
|
||||||
FakeMatrixClient(
|
FakeMatrixClient(
|
||||||
sessionId = A_SESSION_ID,
|
sessionId = A_SESSION_ID,
|
||||||
|
|
@ -119,7 +97,7 @@ class InviteListPresenterTests {
|
||||||
Truth.assertThat(withInviteState.inviteList.size).isEqualTo(1)
|
Truth.assertThat(withInviteState.inviteList.size).isEqualTo(1)
|
||||||
Truth.assertThat(withInviteState.inviteList[0].roomId).isEqualTo(A_ROOM_ID)
|
Truth.assertThat(withInviteState.inviteList[0].roomId).isEqualTo(A_ROOM_ID)
|
||||||
Truth.assertThat(withInviteState.inviteList[0].roomAlias).isEqualTo(A_USER_ID.value)
|
Truth.assertThat(withInviteState.inviteList[0].roomAlias).isEqualTo(A_USER_ID.value)
|
||||||
Truth.assertThat(withInviteState.inviteList[0].roomName).isEqualTo(A_USER_NAME)
|
Truth.assertThat(withInviteState.inviteList[0].roomName).isEqualTo(A_ROOM_NAME)
|
||||||
Truth.assertThat(withInviteState.inviteList[0].roomAvatarData).isEqualTo(
|
Truth.assertThat(withInviteState.inviteList[0].roomAvatarData).isEqualTo(
|
||||||
AvatarData(
|
AvatarData(
|
||||||
id = A_USER_ID.value,
|
id = A_USER_ID.value,
|
||||||
|
|
@ -133,32 +111,8 @@ class InviteListPresenterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - includes sender details for room invites`() = runTest {
|
fun `present - includes sender details for room invites`() = runTest {
|
||||||
val invitesDataSource = FakeRoomSummaryDataSource()
|
val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
||||||
invitesDataSource.postRoomSummary(
|
|
||||||
listOf(
|
|
||||||
RoomSummary.Filled(
|
|
||||||
RoomSummaryDetails(
|
|
||||||
roomId = A_ROOM_ID,
|
|
||||||
name = A_USER_NAME,
|
|
||||||
avatarURLString = null,
|
|
||||||
isDirect = false,
|
|
||||||
lastMessage = null,
|
|
||||||
lastMessageTimestamp = null,
|
|
||||||
unreadNotificationCount = 0,
|
|
||||||
inviter = RoomMember(
|
|
||||||
userId = A_USER_ID,
|
|
||||||
displayName = A_USER_NAME,
|
|
||||||
avatarUrl = AN_AVATAR_URL,
|
|
||||||
membership = RoomMembershipState.JOIN,
|
|
||||||
isNameAmbiguous = false,
|
|
||||||
powerLevel = 0,
|
|
||||||
normalizedPowerLevel = 0,
|
|
||||||
isIgnored = false,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val presenter = InviteListPresenter(
|
val presenter = InviteListPresenter(
|
||||||
FakeMatrixClient(
|
FakeMatrixClient(
|
||||||
sessionId = A_SESSION_ID,
|
sessionId = A_SESSION_ID,
|
||||||
|
|
@ -181,4 +135,307 @@ class InviteListPresenterTests {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - shows confirm dialog for declining direct chat invites`() = runTest {
|
||||||
|
val invitesDataSource = FakeRoomSummaryDataSource().withDirectChatInvitation()
|
||||||
|
|
||||||
|
val presenter = InviteListPresenter(
|
||||||
|
FakeMatrixClient(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
invitesDataSource = invitesDataSource,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val originalState = awaitItem()
|
||||||
|
originalState.eventSink(InviteListEvents.DeclineInvite(originalState.inviteList[0]))
|
||||||
|
|
||||||
|
val newState = awaitItem()
|
||||||
|
Truth.assertThat(newState.declineConfirmationDialog).isInstanceOf(InviteDeclineConfirmationDialog.Visible::class.java)
|
||||||
|
|
||||||
|
val confirmDialog = newState.declineConfirmationDialog as InviteDeclineConfirmationDialog.Visible
|
||||||
|
Truth.assertThat(confirmDialog.isDirect).isTrue()
|
||||||
|
Truth.assertThat(confirmDialog.name).isEqualTo(A_ROOM_NAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - shows confirm dialog for declining room invites`() = runTest {
|
||||||
|
val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
||||||
|
|
||||||
|
val presenter = InviteListPresenter(
|
||||||
|
FakeMatrixClient(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
invitesDataSource = invitesDataSource,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val originalState = awaitItem()
|
||||||
|
originalState.eventSink(InviteListEvents.DeclineInvite(originalState.inviteList[0]))
|
||||||
|
|
||||||
|
val newState = awaitItem()
|
||||||
|
Truth.assertThat(newState.declineConfirmationDialog).isInstanceOf(InviteDeclineConfirmationDialog.Visible::class.java)
|
||||||
|
|
||||||
|
val confirmDialog = newState.declineConfirmationDialog as InviteDeclineConfirmationDialog.Visible
|
||||||
|
Truth.assertThat(confirmDialog.isDirect).isFalse()
|
||||||
|
Truth.assertThat(confirmDialog.name).isEqualTo(A_ROOM_NAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - hides confirm dialog when cancelling`() = runTest {
|
||||||
|
val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
||||||
|
|
||||||
|
val presenter = InviteListPresenter(
|
||||||
|
FakeMatrixClient(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
invitesDataSource = invitesDataSource,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val originalState = awaitItem()
|
||||||
|
originalState.eventSink(InviteListEvents.DeclineInvite(originalState.inviteList[0]))
|
||||||
|
|
||||||
|
skipItems(1)
|
||||||
|
|
||||||
|
originalState.eventSink(InviteListEvents.CancelDeclineInvite)
|
||||||
|
|
||||||
|
val newState = awaitItem()
|
||||||
|
Truth.assertThat(newState.declineConfirmationDialog).isInstanceOf(InviteDeclineConfirmationDialog.Hidden::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - declines invite after confirming`() = runTest {
|
||||||
|
val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
||||||
|
val client = FakeMatrixClient(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
invitesDataSource = invitesDataSource,
|
||||||
|
)
|
||||||
|
val room = FakeMatrixRoom()
|
||||||
|
val presenter = InviteListPresenter(client)
|
||||||
|
client.givenGetRoomResult(A_ROOM_ID, room)
|
||||||
|
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val originalState = awaitItem()
|
||||||
|
originalState.eventSink(InviteListEvents.DeclineInvite(originalState.inviteList[0]))
|
||||||
|
|
||||||
|
skipItems(1)
|
||||||
|
|
||||||
|
originalState.eventSink(InviteListEvents.ConfirmDeclineInvite)
|
||||||
|
|
||||||
|
skipItems(2)
|
||||||
|
|
||||||
|
Truth.assertThat(room.isInviteRejected).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - declines invite after confirming and sets state on error`() = runTest {
|
||||||
|
val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
||||||
|
val client = FakeMatrixClient(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
invitesDataSource = invitesDataSource,
|
||||||
|
)
|
||||||
|
val room = FakeMatrixRoom()
|
||||||
|
val presenter = InviteListPresenter(client)
|
||||||
|
val ex = Throwable("Ruh roh!")
|
||||||
|
room.givenRejectInviteResult(Result.failure(ex))
|
||||||
|
client.givenGetRoomResult(A_ROOM_ID, room)
|
||||||
|
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val originalState = awaitItem()
|
||||||
|
originalState.eventSink(InviteListEvents.DeclineInvite(originalState.inviteList[0]))
|
||||||
|
|
||||||
|
skipItems(1)
|
||||||
|
|
||||||
|
originalState.eventSink(InviteListEvents.ConfirmDeclineInvite)
|
||||||
|
|
||||||
|
skipItems(1)
|
||||||
|
|
||||||
|
val newState = awaitItem()
|
||||||
|
|
||||||
|
Truth.assertThat(room.isInviteRejected).isTrue()
|
||||||
|
Truth.assertThat(newState.declinedAction).isEqualTo(Async.Failure<Unit>(ex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - dismisses declining error state`() = runTest {
|
||||||
|
val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
||||||
|
val client = FakeMatrixClient(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
invitesDataSource = invitesDataSource,
|
||||||
|
)
|
||||||
|
val room = FakeMatrixRoom()
|
||||||
|
val presenter = InviteListPresenter(client)
|
||||||
|
val ex = Throwable("Ruh roh!")
|
||||||
|
room.givenRejectInviteResult(Result.failure(ex))
|
||||||
|
client.givenGetRoomResult(A_ROOM_ID, room)
|
||||||
|
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val originalState = awaitItem()
|
||||||
|
originalState.eventSink(InviteListEvents.DeclineInvite(originalState.inviteList[0]))
|
||||||
|
|
||||||
|
skipItems(1)
|
||||||
|
|
||||||
|
originalState.eventSink(InviteListEvents.ConfirmDeclineInvite)
|
||||||
|
|
||||||
|
skipItems(2)
|
||||||
|
|
||||||
|
originalState.eventSink(InviteListEvents.DismissDeclineError)
|
||||||
|
|
||||||
|
val newState = awaitItem()
|
||||||
|
|
||||||
|
Truth.assertThat(newState.declinedAction).isEqualTo(Async.Uninitialized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - accepts invites and sets state on success`() = runTest {
|
||||||
|
val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
||||||
|
val client = FakeMatrixClient(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
invitesDataSource = invitesDataSource,
|
||||||
|
)
|
||||||
|
val room = FakeMatrixRoom()
|
||||||
|
val presenter = InviteListPresenter(client)
|
||||||
|
client.givenGetRoomResult(A_ROOM_ID, room)
|
||||||
|
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val originalState = awaitItem()
|
||||||
|
originalState.eventSink(InviteListEvents.AcceptInvite(originalState.inviteList[0]))
|
||||||
|
|
||||||
|
val newState = awaitItem()
|
||||||
|
|
||||||
|
Truth.assertThat(room.isInviteAccepted).isTrue()
|
||||||
|
Truth.assertThat(newState.acceptedAction).isEqualTo(Async.Success(A_ROOM_ID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - accepts invites and sets state on error`() = runTest {
|
||||||
|
val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
||||||
|
val client = FakeMatrixClient(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
invitesDataSource = invitesDataSource,
|
||||||
|
)
|
||||||
|
val room = FakeMatrixRoom()
|
||||||
|
val presenter = InviteListPresenter(client)
|
||||||
|
val ex = Throwable("Ruh roh!")
|
||||||
|
room.givenAcceptInviteResult(Result.failure(ex))
|
||||||
|
client.givenGetRoomResult(A_ROOM_ID, room)
|
||||||
|
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val originalState = awaitItem()
|
||||||
|
originalState.eventSink(InviteListEvents.AcceptInvite(originalState.inviteList[0]))
|
||||||
|
|
||||||
|
val newState = awaitItem()
|
||||||
|
|
||||||
|
Truth.assertThat(room.isInviteAccepted).isTrue()
|
||||||
|
Truth.assertThat(newState.acceptedAction).isEqualTo(Async.Failure<RoomId>(ex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - dismisses accepting error state`() = runTest {
|
||||||
|
val invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
||||||
|
val client = FakeMatrixClient(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
invitesDataSource = invitesDataSource,
|
||||||
|
)
|
||||||
|
val room = FakeMatrixRoom()
|
||||||
|
val presenter = InviteListPresenter(client)
|
||||||
|
val ex = Throwable("Ruh roh!")
|
||||||
|
room.givenAcceptInviteResult(Result.failure(ex))
|
||||||
|
client.givenGetRoomResult(A_ROOM_ID, room)
|
||||||
|
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val originalState = awaitItem()
|
||||||
|
originalState.eventSink(InviteListEvents.AcceptInvite(originalState.inviteList[0]))
|
||||||
|
|
||||||
|
skipItems(1)
|
||||||
|
|
||||||
|
originalState.eventSink(InviteListEvents.DismissAcceptError)
|
||||||
|
|
||||||
|
val newState = awaitItem()
|
||||||
|
Truth.assertThat(newState.acceptedAction).isEqualTo(Async.Uninitialized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun FakeRoomSummaryDataSource.withRoomInvitation(): FakeRoomSummaryDataSource {
|
||||||
|
postRoomSummary(
|
||||||
|
listOf(
|
||||||
|
RoomSummary.Filled(
|
||||||
|
RoomSummaryDetails(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
name = A_ROOM_NAME,
|
||||||
|
avatarURLString = null,
|
||||||
|
isDirect = false,
|
||||||
|
lastMessage = null,
|
||||||
|
lastMessageTimestamp = null,
|
||||||
|
unreadNotificationCount = 0,
|
||||||
|
inviter = RoomMember(
|
||||||
|
userId = A_USER_ID,
|
||||||
|
displayName = A_USER_NAME,
|
||||||
|
avatarUrl = AN_AVATAR_URL,
|
||||||
|
membership = RoomMembershipState.JOIN,
|
||||||
|
isNameAmbiguous = false,
|
||||||
|
powerLevel = 0,
|
||||||
|
normalizedPowerLevel = 0,
|
||||||
|
isIgnored = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun FakeRoomSummaryDataSource.withDirectChatInvitation(): FakeRoomSummaryDataSource {
|
||||||
|
postRoomSummary(
|
||||||
|
listOf(
|
||||||
|
RoomSummary.Filled(
|
||||||
|
RoomSummaryDetails(
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
name = A_ROOM_NAME,
|
||||||
|
avatarURLString = null,
|
||||||
|
isDirect = true,
|
||||||
|
lastMessage = null,
|
||||||
|
lastMessageTimestamp = null,
|
||||||
|
unreadNotificationCount = 0,
|
||||||
|
inviter = RoomMember(
|
||||||
|
userId = A_USER_ID,
|
||||||
|
displayName = A_USER_NAME,
|
||||||
|
avatarUrl = AN_AVATAR_URL,
|
||||||
|
membership = RoomMembershipState.JOIN,
|
||||||
|
isNameAmbiguous = false,
|
||||||
|
powerLevel = 0,
|
||||||
|
normalizedPowerLevel = 0,
|
||||||
|
isIgnored = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
|
|
||||||
interface MatrixRoom: Closeable {
|
interface MatrixRoom : Closeable {
|
||||||
val roomId: RoomId
|
val roomId: RoomId
|
||||||
val name: String?
|
val name: String?
|
||||||
val bestName: String
|
val bestName: String
|
||||||
|
|
@ -36,7 +36,7 @@ interface MatrixRoom: Closeable {
|
||||||
val isDirect: Boolean
|
val isDirect: Boolean
|
||||||
val isPublic: Boolean
|
val isPublic: Boolean
|
||||||
|
|
||||||
suspend fun members() : List<RoomMember>
|
suspend fun members(): List<RoomMember>
|
||||||
|
|
||||||
suspend fun memberCount(): Int
|
suspend fun memberCount(): Int
|
||||||
|
|
||||||
|
|
@ -63,4 +63,8 @@ interface MatrixRoom: Closeable {
|
||||||
suspend fun redactEvent(eventId: EventId, reason: String? = null): Result<Unit>
|
suspend fun redactEvent(eventId: EventId, reason: String? = null): Result<Unit>
|
||||||
|
|
||||||
suspend fun leave(): Result<Unit>
|
suspend fun leave(): Result<Unit>
|
||||||
|
|
||||||
|
suspend fun acceptInvitation(): Result<Unit>
|
||||||
|
|
||||||
|
suspend fun rejectInvitation(): Result<Unit>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto
|
||||||
roomId = RoomId(slidingSyncRoom.roomId()),
|
roomId = RoomId(slidingSyncRoom.roomId()),
|
||||||
name = slidingSyncRoom.name() ?: slidingSyncRoom.roomId(),
|
name = slidingSyncRoom.name() ?: slidingSyncRoom.roomId(),
|
||||||
canonicalAlias = room?.canonicalAlias(),
|
canonicalAlias = room?.canonicalAlias(),
|
||||||
isDirect = slidingSyncRoom.isDm() ?: false,
|
isDirect = room?.isDirect() ?: false,
|
||||||
avatarURLString = room?.avatarUrl(),
|
avatarURLString = room?.avatarUrl(),
|
||||||
unreadNotificationCount = slidingSyncRoom.unreadNotifications().use { it.notificationCount().toInt() },
|
unreadNotificationCount = slidingSyncRoom.unreadNotifications().use { it.notificationCount().toInt() },
|
||||||
lastMessage = latestRoomMessage,
|
lastMessage = latestRoomMessage,
|
||||||
|
|
|
||||||
|
|
@ -206,4 +206,17 @@ class RustMatrixRoom(
|
||||||
innerRoom.leave()
|
innerRoom.leave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun acceptInvitation(): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||||
|
kotlin.runCatching {
|
||||||
|
innerRoom.acceptInvitation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun rejectInvitation(): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||||
|
kotlin.runCatching {
|
||||||
|
innerRoom.rejectInvitation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,10 @@ class FakeMatrixClient(
|
||||||
private var createDmFailure: Throwable? = null
|
private var createDmFailure: Throwable? = null
|
||||||
private var findDmResult: MatrixRoom? = FakeMatrixRoom()
|
private var findDmResult: MatrixRoom? = FakeMatrixRoom()
|
||||||
private var logoutFailure: Throwable? = null
|
private var logoutFailure: Throwable? = null
|
||||||
|
private val getRoomResults = mutableMapOf<RoomId, MatrixRoom>()
|
||||||
|
|
||||||
override fun getRoom(roomId: RoomId): MatrixRoom? {
|
override fun getRoom(roomId: RoomId): MatrixRoom? {
|
||||||
return FakeMatrixRoom(roomId)
|
return getRoomResults[roomId]
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findDM(userId: UserId): MatrixRoom? {
|
override fun findDM(userId: UserId): MatrixRoom? {
|
||||||
|
|
@ -136,4 +137,8 @@ class FakeMatrixClient(
|
||||||
fun givenFindDmResult(result: MatrixRoom?) {
|
fun givenFindDmResult(result: MatrixRoom?) {
|
||||||
findDmResult = result
|
findDmResult = result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenGetRoomResult(roomId: RoomId, result: MatrixRoom) {
|
||||||
|
getRoomResults[roomId] = result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ 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.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
|
import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
|
||||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
|
@ -46,12 +46,20 @@ class FakeMatrixRoom(
|
||||||
|
|
||||||
private var userDisplayNameResult = Result.success<String?>(null)
|
private var userDisplayNameResult = Result.success<String?>(null)
|
||||||
private var userAvatarUrlResult = Result.success<String?>(null)
|
private var userAvatarUrlResult = Result.success<String?>(null)
|
||||||
|
private var acceptInviteResult = Result.success(Unit)
|
||||||
|
private var rejectInviteResult = Result.success(Unit)
|
||||||
private var dmMember: RoomMember? = null
|
private var dmMember: RoomMember? = null
|
||||||
private var fetchMemberResult: Result<Unit> = Result.success(Unit)
|
private var fetchMemberResult: Result<Unit> = Result.success(Unit)
|
||||||
|
|
||||||
var areMembersFetched: Boolean = false
|
var areMembersFetched: Boolean = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var isInviteAccepted: Boolean = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
var isInviteRejected: Boolean = false
|
||||||
|
private set
|
||||||
|
|
||||||
private var leaveRoomError: Throwable? = null
|
private var leaveRoomError: Throwable? = null
|
||||||
|
|
||||||
override fun syncUpdateFlow(): Flow<Long> {
|
override fun syncUpdateFlow(): Flow<Long> {
|
||||||
|
|
@ -131,6 +139,15 @@ class FakeMatrixRoom(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun leave(): Result<Unit> = leaveRoomError?.let { Result.failure(it) } ?: Result.success(Unit)
|
override suspend fun leave(): Result<Unit> = leaveRoomError?.let { Result.failure(it) } ?: Result.success(Unit)
|
||||||
|
override suspend fun acceptInvitation(): Result<Unit> {
|
||||||
|
isInviteAccepted = true
|
||||||
|
return acceptInviteResult
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun rejectInvitation(): Result<Unit> {
|
||||||
|
isInviteRejected = true
|
||||||
|
return rejectInviteResult
|
||||||
|
}
|
||||||
|
|
||||||
override fun close() = Unit
|
override fun close() = Unit
|
||||||
|
|
||||||
|
|
@ -153,4 +170,13 @@ class FakeMatrixRoom(
|
||||||
fun givenUserAvatarUrlResult(avatarUrl: Result<String?>) {
|
fun givenUserAvatarUrlResult(avatarUrl: Result<String?>) {
|
||||||
userAvatarUrlResult = avatarUrl
|
userAvatarUrlResult = avatarUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenAcceptInviteResult(result: Result<Unit>) {
|
||||||
|
acceptInviteResult = result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenRejectInviteResult(result: Result<Unit>) {
|
||||||
|
rejectInviteResult = result
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:e8e44bafcf2fd7119562d6a92a8deff74a7ba470b4a7a87951cf77e19eee2eb0
|
||||||
|
size 43576
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:b72ad15218e4f17b1c47018925194f3b21c70630bf8744395352707dc257ab70
|
||||||
|
size 44025
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ac02a683f4a6297427e6e74bd331c45e9ee7efb044767568bc46566ae46f2274
|
||||||
|
size 39618
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ac02a683f4a6297427e6e74bd331c45e9ee7efb044767568bc46566ae46f2274
|
||||||
|
size 39618
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:b8e00ba1ff01242bf3e1f4858cb66b5ed87681aec76090a545e8d729361e5e37
|
||||||
|
size 43034
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:27b3164386391fc115d3ecdcf202495a3f03509c2c2c5564880ed756d56ed3b5
|
||||||
|
size 43463
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:64ec3959fddb9dccc271ac0fa28275fa43df893f8f672b99f9f594004cef438e
|
||||||
|
size 39052
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:64ec3959fddb9dccc271ac0fa28275fa43df893f8f672b99f9f594004cef438e
|
||||||
|
size 39052
|
||||||
Loading…
Add table
Add a link
Reference in a new issue