Merge develop into feature/fga/some_room_related_fixes
This commit is contained in:
commit
1d690fdd00
32 changed files with 594 additions and 88 deletions
|
|
@ -24,4 +24,5 @@ android {
|
|||
|
||||
dependencies {
|
||||
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.plugin.Plugin
|
||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
interface InviteListEntryPoint : FeatureEntryPoint {
|
||||
|
||||
|
|
@ -32,6 +33,8 @@ interface InviteListEntryPoint : FeatureEntryPoint {
|
|||
|
||||
interface Callback : Plugin {
|
||||
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.features.invitelist.api.InviteListEntryPoint
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class InviteListNode @AssistedInject constructor(
|
||||
|
|
@ -39,12 +40,17 @@ class InviteListNode @AssistedInject constructor(
|
|||
plugins<InviteListEntryPoint.Callback>().forEach { it.onBackClicked() }
|
||||
}
|
||||
|
||||
private fun onInviteAccepted(roomId: RoomId) {
|
||||
plugins<InviteListEntryPoint.Callback>().forEach { it.onInviteAccepted(roomId) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
InviteListView(
|
||||
state = state,
|
||||
onBackClicked = ::onBackClicked,
|
||||
onInviteAccepted = ::onInviteAccepted,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,15 +17,24 @@
|
|||
package io.element.android.features.invitelist.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
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.InviteSender
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.execute
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
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 kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class InviteListPresenter @Inject constructor(
|
||||
|
|
@ -39,11 +48,71 @@ class InviteListPresenter @Inject constructor(
|
|||
.roomSummaries()
|
||||
.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(
|
||||
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? {
|
||||
return when (roomSummary) {
|
||||
is RoomSummary.Filled -> roomSummary.details.run {
|
||||
|
|
@ -71,6 +140,7 @@ class InviteListPresenter @Inject constructor(
|
|||
roomName = name,
|
||||
roomAlias = alias,
|
||||
roomAvatarData = avatarData,
|
||||
isDirect = isDirect,
|
||||
sender = if (isDirect) null else inviter?.let {
|
||||
InviteSender(
|
||||
userId = it.userId,
|
||||
|
|
@ -81,9 +151,10 @@ class InviteListPresenter @Inject constructor(
|
|||
url = it.avatarUrl,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,20 @@ package io.element.android.features.invitelist.impl
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
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
|
||||
|
||||
@Immutable
|
||||
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 io.element.android.features.invitelist.impl.model.InviteListInviteSummary
|
||||
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.UserId
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -28,7 +29,11 @@ open class InviteListStateProvider : PreviewParameterProvider<InviteListState> {
|
|||
override val values: Sequence<InviteListState>
|
||||
get() = sequenceOf(
|
||||
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.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.unit.dp
|
||||
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.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.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
|
|
@ -47,16 +51,56 @@ fun InviteListView(
|
|||
state: InviteListState,
|
||||
modifier: Modifier = Modifier,
|
||||
onBackClicked: () -> Unit = {},
|
||||
onAcceptClicked: (RoomId) -> Unit = {},
|
||||
onDeclineClicked: (RoomId) -> Unit = {},
|
||||
onInviteAccepted: (RoomId) -> Unit = {},
|
||||
) {
|
||||
if (state.acceptedAction is Async.Success) {
|
||||
LaunchedEffect(state.acceptedAction) {
|
||||
onInviteAccepted(state.acceptedAction.state)
|
||||
}
|
||||
}
|
||||
|
||||
InviteListContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
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)
|
||||
|
|
@ -65,8 +109,6 @@ fun InviteListContent(
|
|||
state: InviteListState,
|
||||
modifier: Modifier = Modifier,
|
||||
onBackClicked: () -> Unit = {},
|
||||
onAcceptClicked: (RoomId) -> Unit = {},
|
||||
onDeclineClicked: (RoomId) -> Unit = {},
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
|
|
@ -102,8 +144,8 @@ fun InviteListContent(
|
|||
) { invite ->
|
||||
InviteSummaryRow(
|
||||
invite = invite,
|
||||
onAcceptClicked = onAcceptClicked,
|
||||
onDeclineClicked = onDeclineClicked,
|
||||
onAcceptClicked = { state.eventSink(InviteListEvents.AcceptInvite(invite)) },
|
||||
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.OutlinedButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import io.element.android.libraries.ui.strings.R as StringR
|
||||
|
||||
|
|
@ -69,8 +68,8 @@ private val minHeight = 72.dp
|
|||
internal fun InviteSummaryRow(
|
||||
invite: InviteListInviteSummary,
|
||||
modifier: Modifier = Modifier,
|
||||
onAcceptClicked: (RoomId) -> Unit = {},
|
||||
onDeclineClicked: (RoomId) -> Unit = {},
|
||||
onAcceptClicked: () -> Unit = {},
|
||||
onDeclineClicked: () -> Unit = {},
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
|
|
@ -88,8 +87,8 @@ internal fun InviteSummaryRow(
|
|||
@Composable
|
||||
internal fun DefaultInviteSummaryRow(
|
||||
invite: InviteListInviteSummary,
|
||||
onAcceptClicked: (RoomId) -> Unit = {},
|
||||
onDeclineClicked: (RoomId) -> Unit = {},
|
||||
onAcceptClicked: () -> Unit = {},
|
||||
onDeclineClicked: () -> Unit = {},
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -138,7 +137,7 @@ internal fun DefaultInviteSummaryRow(
|
|||
Row(Modifier.padding(top = 12.dp)) {
|
||||
OutlinedButton(
|
||||
content = { Text(stringResource(StringR.string.action_decline), style = ElementTextStyles.Button) },
|
||||
onClick = { onDeclineClicked(invite.roomId) },
|
||||
onClick = onDeclineClicked,
|
||||
modifier = Modifier.weight(1f),
|
||||
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 7.dp),
|
||||
)
|
||||
|
|
@ -147,7 +146,7 @@ internal fun DefaultInviteSummaryRow(
|
|||
|
||||
Button(
|
||||
content = { Text(stringResource(StringR.string.action_accept), style = ElementTextStyles.Button) },
|
||||
onClick = { onAcceptClicked(invite.roomId) },
|
||||
onClick = onAcceptClicked,
|
||||
modifier = Modifier.weight(1f),
|
||||
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 7.dp),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ data class InviteListInviteSummary(
|
|||
val roomName: String = "",
|
||||
val roomAlias: String? = null,
|
||||
val roomAvatarData: AvatarData = AvatarData(roomId.value, roomName),
|
||||
val sender: InviteSender? = null
|
||||
val sender: InviteSender? = null,
|
||||
val isDirect: Boolean = false
|
||||
)
|
||||
|
||||
data class InviteSender(
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ import app.cash.molecule.RecompositionClock
|
|||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
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.matrix.api.core.RoomId
|
||||
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.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_NAME
|
||||
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 kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -80,32 +83,7 @@ class InviteListPresenterTests {
|
|||
|
||||
@Test
|
||||
fun `present - uses user ID and avatar for direct invites`() = runTest {
|
||||
val invitesDataSource = FakeRoomSummaryDataSource()
|
||||
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 invitesDataSource = FakeRoomSummaryDataSource().withDirectChatInvitation()
|
||||
val presenter = InviteListPresenter(
|
||||
FakeMatrixClient(
|
||||
sessionId = A_SESSION_ID,
|
||||
|
|
@ -119,7 +97,7 @@ class InviteListPresenterTests {
|
|||
Truth.assertThat(withInviteState.inviteList.size).isEqualTo(1)
|
||||
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].roomName).isEqualTo(A_USER_NAME)
|
||||
Truth.assertThat(withInviteState.inviteList[0].roomName).isEqualTo(A_ROOM_NAME)
|
||||
Truth.assertThat(withInviteState.inviteList[0].roomAvatarData).isEqualTo(
|
||||
AvatarData(
|
||||
id = A_USER_ID.value,
|
||||
|
|
@ -133,32 +111,8 @@ class InviteListPresenterTests {
|
|||
|
||||
@Test
|
||||
fun `present - includes sender details for room invites`() = runTest {
|
||||
val invitesDataSource = FakeRoomSummaryDataSource()
|
||||
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 invitesDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
|
||||
|
||||
val presenter = InviteListPresenter(
|
||||
FakeMatrixClient(
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue