Merge pull request #4353 from element-hq/feature/fga/room_preview_invite_state
[Change] Invited state room preview
This commit is contained in:
commit
0d1a35970d
28 changed files with 327 additions and 144 deletions
|
|
@ -8,6 +8,6 @@
|
|||
package io.element.android.features.invite.api.response
|
||||
|
||||
interface AcceptDeclineInviteEvents {
|
||||
data class AcceptInvite(val invite: InviteData) : AcceptDeclineInviteEvents
|
||||
data class DeclineInvite(val invite: InviteData) : AcceptDeclineInviteEvents
|
||||
data class AcceptInvite(val invite: InviteData?) : AcceptDeclineInviteEvents
|
||||
data class DeclineInvite(val invite: InviteData?, val blockUser: Boolean = false) : AcceptDeclineInviteEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ package io.element.android.features.invite.api.response
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
open class AcceptDeclineInviteStateProvider : PreviewParameterProvider<AcceptDeclineInviteState> {
|
||||
override val values: Sequence<AcceptDeclineInviteState>
|
||||
|
|
@ -17,12 +18,20 @@ open class AcceptDeclineInviteStateProvider : PreviewParameterProvider<AcceptDec
|
|||
anAcceptDeclineInviteState(),
|
||||
anAcceptDeclineInviteState(
|
||||
declineAction = ConfirmingDeclineInvite(
|
||||
InviteData(RoomId("!room:matrix.org"), isDm = true, roomName = "Alice")
|
||||
InviteData(roomId = RoomId("!room:matrix.org"), isDm = true, roomName = "Alice", senderId = UserId("@alice:matrix.org")),
|
||||
blockUser = false,
|
||||
),
|
||||
),
|
||||
anAcceptDeclineInviteState(
|
||||
declineAction = ConfirmingDeclineInvite(
|
||||
InviteData(RoomId("!room:matrix.org"), isDm = false, roomName = "Some room")
|
||||
InviteData(roomId = RoomId("!room:matrix.org"), isDm = false, roomName = "Some room", senderId = UserId("@alice:matrix.org")),
|
||||
blockUser = false,
|
||||
),
|
||||
),
|
||||
anAcceptDeclineInviteState(
|
||||
declineAction = ConfirmingDeclineInvite(
|
||||
InviteData(roomId = RoomId("!room:matrix.org"), isDm = true, roomName = "Alice", senderId = UserId("@alice:matrix.org")),
|
||||
blockUser = true,
|
||||
),
|
||||
),
|
||||
anAcceptDeclineInviteState(
|
||||
|
|
|
|||
|
|
@ -11,4 +11,5 @@ import io.element.android.libraries.architecture.AsyncAction
|
|||
|
||||
data class ConfirmingDeclineInvite(
|
||||
val inviteData: InviteData,
|
||||
val blockUser: Boolean,
|
||||
) : AsyncAction.Confirming
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@
|
|||
package io.element.android.features.invite.api.response
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
data class InviteData(
|
||||
val senderId: UserId,
|
||||
val roomId: RoomId,
|
||||
val roomName: String,
|
||||
val isDm: Boolean,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import im.vector.app.features.analytics.plan.JoinedRoom
|
|||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.ConfirmingDeclineInvite
|
||||
import io.element.android.features.invite.api.response.InviteData
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
|
|
@ -43,15 +44,34 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
|||
fun handleEvents(event: AcceptDeclineInviteEvents) {
|
||||
when (event) {
|
||||
is AcceptDeclineInviteEvents.AcceptInvite -> {
|
||||
localCoroutineScope.acceptInvite(event.invite.roomId, acceptedAction)
|
||||
val inviteData = event.invite
|
||||
if (inviteData == null) {
|
||||
acceptedAction.value = AsyncAction.Failure(InvalidDataException())
|
||||
} else {
|
||||
localCoroutineScope.acceptInvite(inviteData.roomId, acceptedAction)
|
||||
}
|
||||
}
|
||||
|
||||
is AcceptDeclineInviteEvents.DeclineInvite -> {
|
||||
declinedAction.value = ConfirmingDeclineInvite(event.invite)
|
||||
val inviteData = event.invite
|
||||
if (inviteData == null) {
|
||||
declinedAction.value = AsyncAction.Failure(InvalidDataException())
|
||||
} else {
|
||||
declinedAction.value = ConfirmingDeclineInvite(inviteData, event.blockUser)
|
||||
}
|
||||
}
|
||||
|
||||
is InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite -> {
|
||||
localCoroutineScope.declineInvite(event.roomId, declinedAction)
|
||||
when (val declinedActionValue = declinedAction.value) {
|
||||
is ConfirmingDeclineInvite -> {
|
||||
localCoroutineScope.declineInvite(
|
||||
inviteData = declinedActionValue.inviteData,
|
||||
declinedAction = declinedAction,
|
||||
blockUser = declinedActionValue.blockUser,
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
is InternalAcceptDeclineInviteEvents.CancelDeclineInvite -> {
|
||||
|
|
@ -92,13 +112,20 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.declineInvite(roomId: RoomId, declinedAction: MutableState<AsyncAction<RoomId>>) = launch {
|
||||
private fun CoroutineScope.declineInvite(
|
||||
inviteData: InviteData,
|
||||
blockUser: Boolean,
|
||||
declinedAction: MutableState<AsyncAction<RoomId>>,
|
||||
) = launch {
|
||||
suspend {
|
||||
client.getPendingRoom(roomId)?.use {
|
||||
client.getPendingRoom(inviteData.roomId)?.use {
|
||||
it.leave().getOrThrow()
|
||||
notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, roomId)
|
||||
}
|
||||
roomId
|
||||
if (blockUser) {
|
||||
client.ignoreUser(inviteData.senderId).getOrThrow()
|
||||
}
|
||||
notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, inviteData.roomId)
|
||||
inviteData.roomId
|
||||
}.runCatchingUpdatingState(declinedAction)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,8 +50,9 @@ fun AcceptDeclineInviteView(
|
|||
if (confirming is ConfirmingDeclineInvite) {
|
||||
DeclineConfirmationDialog(
|
||||
invite = confirming.inviteData,
|
||||
blockUser = confirming.blockUser,
|
||||
onConfirmClick = {
|
||||
state.eventSink(InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite(confirming.inviteData.roomId))
|
||||
state.eventSink(InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite)
|
||||
},
|
||||
onDismissClick = {
|
||||
state.eventSink(InternalAcceptDeclineInviteEvents.CancelDeclineInvite)
|
||||
|
|
@ -66,29 +67,35 @@ fun AcceptDeclineInviteView(
|
|||
@Composable
|
||||
private fun DeclineConfirmationDialog(
|
||||
invite: InviteData,
|
||||
blockUser: Boolean,
|
||||
onConfirmClick: () -> Unit,
|
||||
onDismissClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val contentResource = if (invite.isDm) {
|
||||
R.string.screen_invites_decline_direct_chat_message
|
||||
} else {
|
||||
R.string.screen_invites_decline_chat_message
|
||||
val senderId = invite.senderId.value
|
||||
val content = when {
|
||||
blockUser -> stringResource(R.string.screen_join_room_decline_and_block_alert_message, senderId)
|
||||
invite.isDm -> stringResource(R.string.screen_invites_decline_direct_chat_message, invite.roomName)
|
||||
else -> stringResource(R.string.screen_invites_decline_chat_message, invite.roomName)
|
||||
}
|
||||
|
||||
val titleResource = if (invite.isDm) {
|
||||
R.string.screen_invites_decline_direct_chat_title
|
||||
} else {
|
||||
R.string.screen_invites_decline_chat_title
|
||||
val title = when {
|
||||
blockUser -> stringResource(R.string.screen_join_room_decline_and_block_alert_title)
|
||||
invite.isDm -> stringResource(R.string.screen_invites_decline_direct_chat_title)
|
||||
else -> stringResource(R.string.screen_invites_decline_chat_title)
|
||||
}
|
||||
val submitText = if (blockUser) {
|
||||
stringResource(R.string.screen_join_room_decline_and_block_alert_confirmation)
|
||||
} else {
|
||||
stringResource(CommonStrings.action_decline)
|
||||
}
|
||||
|
||||
ConfirmationDialog(
|
||||
modifier = modifier,
|
||||
content = stringResource(contentResource, invite.roomName),
|
||||
title = stringResource(titleResource),
|
||||
submitText = stringResource(CommonStrings.action_decline),
|
||||
content = content,
|
||||
title = title,
|
||||
submitText = submitText,
|
||||
cancelText = stringResource(CommonStrings.action_cancel),
|
||||
onSubmitClick = onConfirmClick,
|
||||
destructiveSubmit = blockUser,
|
||||
onDismiss = onDismissClick,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,9 @@
|
|||
package io.element.android.features.invite.impl.response
|
||||
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
sealed interface InternalAcceptDeclineInviteEvents : AcceptDeclineInviteEvents {
|
||||
data class ConfirmDeclineInvite(val roomId: RoomId) : InternalAcceptDeclineInviteEvents
|
||||
data object ConfirmDeclineInvite : InternalAcceptDeclineInviteEvents
|
||||
data object CancelDeclineInvite : InternalAcceptDeclineInviteEvents
|
||||
data object DismissAcceptError : InternalAcceptDeclineInviteEvents
|
||||
data object DismissDeclineError : InternalAcceptDeclineInviteEvents
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.invite.impl.response
|
||||
|
||||
class InvalidDataException : Exception()
|
||||
|
|
@ -6,4 +6,8 @@
|
|||
<string name="screen_invites_decline_direct_chat_title">"Decline chat"</string>
|
||||
<string name="screen_invites_empty_list">"No Invites"</string>
|
||||
<string name="screen_invites_invited_you">"%1$s (%2$s) invited you"</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_confirmation">"Yes, decline & block"</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_message">"Are you sure you want to decline the invite to join this room? This will also prevent %1$s from contacting you or inviting you to rooms."</string>
|
||||
<string name="screen_join_room_decline_and_block_alert_title">"Decline invite & block"</string>
|
||||
<string name="screen_join_room_decline_and_block_button_title">"Decline and block"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ 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.RoomIdOrAlias
|
||||
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.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||
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.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.room.FakeRoomPreview
|
||||
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
|
||||
|
|
@ -61,7 +63,7 @@ class AcceptDeclineInvitePresenterTest {
|
|||
)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData))
|
||||
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData, false))
|
||||
state.eventSink(
|
||||
InternalAcceptDeclineInviteEvents.CancelDeclineInvite
|
||||
)
|
||||
|
|
@ -91,9 +93,9 @@ class AcceptDeclineInvitePresenterTest {
|
|||
)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData))
|
||||
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData, false))
|
||||
state.eventSink(
|
||||
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite(inviteData.roomId)
|
||||
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite
|
||||
)
|
||||
}
|
||||
assertThat(awaitItem().declineAction.isLoading()).isTrue()
|
||||
|
|
@ -139,9 +141,9 @@ class AcceptDeclineInvitePresenterTest {
|
|||
)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData))
|
||||
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData, false))
|
||||
state.eventSink(
|
||||
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite(inviteData.roomId)
|
||||
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite
|
||||
)
|
||||
}
|
||||
assertThat(awaitItem().declineAction.isLoading()).isTrue()
|
||||
|
|
@ -156,6 +158,80 @@ class AcceptDeclineInvitePresenterTest {
|
|||
.with(value(A_SESSION_ID), value(A_ROOM_ID))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - declining invite with block success flow`() = runTest {
|
||||
val clearMembershipNotificationForRoomLambda = lambdaRecorder<SessionId, RoomId, Unit> { _, _ ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val fakeNotificationCleaner = FakeNotificationCleaner(
|
||||
clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda
|
||||
)
|
||||
val declineInviteSuccess = lambdaRecorder { -> Result.success(Unit) }
|
||||
val ignoreUserSuccess = lambdaRecorder { _: UserId -> Result.success(Unit) }
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewResult = { _, _ ->
|
||||
Result.success(FakeRoomPreview(declineInviteResult = declineInviteSuccess))
|
||||
},
|
||||
ignoreUserResult = ignoreUserSuccess
|
||||
)
|
||||
val presenter = createAcceptDeclineInvitePresenter(
|
||||
client = client,
|
||||
notificationCleaner = fakeNotificationCleaner,
|
||||
)
|
||||
presenter.test {
|
||||
val inviteData = anInviteData()
|
||||
awaitItem().also { state ->
|
||||
state.eventSink(
|
||||
AcceptDeclineInviteEvents.DeclineInvite(inviteData, blockUser = true)
|
||||
)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData, true))
|
||||
state.eventSink(
|
||||
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite
|
||||
)
|
||||
}
|
||||
assertThat(awaitItem().declineAction.isLoading()).isTrue()
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.declineAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||
}
|
||||
cancelAndConsumeRemainingEvents()
|
||||
}
|
||||
declineInviteSuccess.assertions().isCalledOnce()
|
||||
ignoreUserSuccess.assertions().isCalledOnce().with(value(A_USER_ID))
|
||||
clearMembershipNotificationForRoomLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_SESSION_ID), value(A_ROOM_ID))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - declining invite with block error flow`() = runTest {
|
||||
val declineInviteFailure = lambdaRecorder { ->
|
||||
Result.failure<Unit>(RuntimeException("Failed to leave room"))
|
||||
}
|
||||
val client = FakeMatrixClient(
|
||||
getRoomPreviewResult = { _, _ ->
|
||||
Result.success(FakeRoomPreview(declineInviteResult = declineInviteFailure))
|
||||
}
|
||||
)
|
||||
val presenter = createAcceptDeclineInvitePresenter(client = client)
|
||||
presenter.test {
|
||||
val inviteData = anInviteData()
|
||||
awaitItem().also { state ->
|
||||
state.eventSink(
|
||||
AcceptDeclineInviteEvents.DeclineInvite(inviteData, blockUser = true)
|
||||
)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData, true))
|
||||
state.eventSink(
|
||||
InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite
|
||||
)
|
||||
}
|
||||
assertThat(awaitItem().declineAction.isLoading()).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - accepting invite error flow`() = runTest {
|
||||
val joinRoomFailure = lambdaRecorder { roomIdOrAlias: RoomIdOrAlias, _: List<String>, _: JoinedRoom.Trigger ->
|
||||
|
|
@ -237,12 +313,14 @@ class AcceptDeclineInvitePresenterTest {
|
|||
private fun anInviteData(
|
||||
roomId: RoomId = A_ROOM_ID,
|
||||
name: String = A_ROOM_NAME,
|
||||
isDm: Boolean = false
|
||||
isDm: Boolean = false,
|
||||
senderId: UserId = A_USER_ID,
|
||||
): InviteData {
|
||||
return InviteData(
|
||||
roomId = roomId,
|
||||
roomName = name,
|
||||
isDm = isDm
|
||||
isDm = isDm,
|
||||
senderId = senderId,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,5 +17,5 @@ sealed interface JoinRoomEvents {
|
|||
data class UpdateKnockMessage(val message: String) : JoinRoomEvents
|
||||
data object ClearActionStates : JoinRoomEvents
|
||||
data object AcceptInvite : JoinRoomEvents
|
||||
data object DeclineInvite : JoinRoomEvents
|
||||
data class DeclineInvite(val blockUser: Boolean) : JoinRoomEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,15 +152,15 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction)
|
||||
is JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction, knockMessage)
|
||||
JoinRoomEvents.AcceptInvite -> {
|
||||
val inviteData = contentState.toInviteData() ?: return
|
||||
val inviteData = contentState.toInviteData()
|
||||
acceptDeclineInviteState.eventSink(
|
||||
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
|
||||
)
|
||||
}
|
||||
JoinRoomEvents.DeclineInvite -> {
|
||||
val inviteData = contentState.toInviteData() ?: return
|
||||
is JoinRoomEvents.DeclineInvite -> {
|
||||
val inviteData = contentState.toInviteData()
|
||||
acceptDeclineInviteState.eventSink(
|
||||
AcceptDeclineInviteEvents.DeclineInvite(inviteData)
|
||||
AcceptDeclineInviteEvents.DeclineInvite(invite = inviteData, blockUser = event.blockUser)
|
||||
)
|
||||
}
|
||||
is JoinRoomEvents.CancelKnock -> coroutineScope.cancelKnockRoom(event.requiresConfirmation, cancelKnockAction)
|
||||
|
|
@ -314,12 +314,19 @@ private fun JoinRule?.toJoinAuthorisationStatus(): JoinAuthorisationStatus {
|
|||
@VisibleForTesting
|
||||
internal fun ContentState.toInviteData(): InviteData? {
|
||||
return when (this) {
|
||||
is ContentState.Loaded -> InviteData(
|
||||
roomId = roomId,
|
||||
// Note: name should not be null at this point, but use Id just in case...
|
||||
roomName = name ?: roomId.value,
|
||||
isDm = isDm
|
||||
)
|
||||
is ContentState.Loaded -> {
|
||||
if (joinAuthorisationStatus is JoinAuthorisationStatus.IsInvited && joinAuthorisationStatus.inviteSender != null) {
|
||||
InviteData(
|
||||
roomId = roomId,
|
||||
// Note: name should not be null at this point, but use Id just in case...
|
||||
roomName = name ?: roomId.value,
|
||||
senderId = joinAuthorisationStatus.inviteSender.userId,
|
||||
isDm = isDm
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
|||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
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.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
|
|
@ -105,8 +106,8 @@ fun JoinRoomView(
|
|||
onAcceptInvite = {
|
||||
state.eventSink(JoinRoomEvents.AcceptInvite)
|
||||
},
|
||||
onDeclineInvite = {
|
||||
state.eventSink(JoinRoomEvents.DeclineInvite)
|
||||
onDeclineInvite = { blockUser ->
|
||||
state.eventSink(JoinRoomEvents.DeclineInvite(blockUser))
|
||||
},
|
||||
onJoinRoom = {
|
||||
state.eventSink(JoinRoomEvents.JoinRoom)
|
||||
|
|
@ -183,7 +184,7 @@ fun JoinRoomView(
|
|||
private fun JoinRoomFooter(
|
||||
joinAuthorisationStatus: JoinAuthorisationStatus,
|
||||
onAcceptInvite: () -> Unit,
|
||||
onDeclineInvite: () -> Unit,
|
||||
onDeclineInvite: (Boolean) -> Unit,
|
||||
onJoinRoom: () -> Unit,
|
||||
onKnockRoom: () -> Unit,
|
||||
onCancelKnock: () -> Unit,
|
||||
|
|
@ -193,23 +194,32 @@ private fun JoinRoomFooter(
|
|||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
) {
|
||||
when (joinAuthorisationStatus) {
|
||||
is JoinAuthorisationStatus.IsInvited -> {
|
||||
ButtonRowMolecule(horizontalArrangement = Arrangement.spacedBy(20.dp)) {
|
||||
OutlinedButton(
|
||||
text = stringResource(CommonStrings.action_decline),
|
||||
onClick = onDeclineInvite,
|
||||
modifier = Modifier.weight(1f),
|
||||
size = ButtonSize.LargeLowPadding,
|
||||
)
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_accept),
|
||||
onClick = onAcceptInvite,
|
||||
modifier = Modifier.weight(1f),
|
||||
size = ButtonSize.LargeLowPadding,
|
||||
Column {
|
||||
ButtonRowMolecule(horizontalArrangement = Arrangement.spacedBy(20.dp)) {
|
||||
OutlinedButton(
|
||||
text = stringResource(CommonStrings.action_decline),
|
||||
onClick = { onDeclineInvite(false) },
|
||||
modifier = Modifier.weight(1f),
|
||||
size = ButtonSize.LargeLowPadding,
|
||||
)
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_accept),
|
||||
onClick = onAcceptInvite,
|
||||
modifier = Modifier.weight(1f),
|
||||
size = ButtonSize.LargeLowPadding,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
TextButton(
|
||||
text = stringResource(R.string.screen_join_room_decline_and_block_button_title),
|
||||
onClick = { onDeclineInvite(true) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
destructive = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -372,12 +382,19 @@ private fun JoinRoomContent(
|
|||
IsKnockedLoadedContent()
|
||||
}
|
||||
else -> {
|
||||
DefaultLoadedContent(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||
contentState = contentState,
|
||||
knockMessage = knockMessage,
|
||||
onKnockMessageUpdate = onKnockMessageUpdate
|
||||
)
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
val inviteSender = (contentState.joinAuthorisationStatus as? JoinAuthorisationStatus.IsInvited)?.inviteSender
|
||||
if (inviteSender != null) {
|
||||
InviteSenderView(inviteSender = inviteSender)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
DefaultLoadedContent(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||
contentState = contentState,
|
||||
knockMessage = knockMessage,
|
||||
onKnockMessageUpdate = onKnockMessageUpdate
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -440,8 +457,8 @@ private fun IncompleteContent(
|
|||
private fun IsKnockedLoadedContent(modifier: Modifier = Modifier) {
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.fillMaxHeight()
|
||||
.padding(horizontal = 16.dp),
|
||||
.fillMaxHeight()
|
||||
.padding(horizontal = 16.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
IconTitleSubtitleMolecule(
|
||||
|
|
@ -487,10 +504,6 @@ private fun DefaultLoadedContent(
|
|||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
val inviteSender = (contentState.joinAuthorisationStatus as? JoinAuthorisationStatus.IsInvited)?.inviteSender
|
||||
if (inviteSender != null) {
|
||||
InviteSenderView(inviteSender = inviteSender)
|
||||
}
|
||||
RoomPreviewDescriptionAtom(contentState.topic ?: "")
|
||||
if (contentState.joinAuthorisationStatus is JoinAuthorisationStatus.CanKnock) {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
|
|
|||
|
|
@ -167,9 +167,9 @@ class JoinRoomPresenterTest {
|
|||
|
||||
awaitItem().also { state ->
|
||||
state.eventSink(JoinRoomEvents.AcceptInvite)
|
||||
state.eventSink(JoinRoomEvents.DeclineInvite)
|
||||
state.eventSink(JoinRoomEvents.DeclineInvite(false))
|
||||
|
||||
val inviteData = state.contentState.toInviteData()!!
|
||||
val inviteData = state.contentState.toInviteData()
|
||||
|
||||
assert(eventSinkRecorder)
|
||||
.isCalledExactly(2)
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ class JoinRoomViewTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Accept invitation IsInvited room emits the expected Event`() {
|
||||
fun `clicking on Accept when JoinAuthorisationStatus is IsInvited emits the expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||
rule.setJoinRoomView(
|
||||
aJoinRoomState(
|
||||
|
|
@ -152,7 +152,7 @@ class JoinRoomViewTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Decline invitation on IsInvited room emits the expected Event`() {
|
||||
fun `clicking on Decline when JoinAuthorisationStatus is IsInvited emits the expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||
rule.setJoinRoomView(
|
||||
aJoinRoomState(
|
||||
|
|
@ -161,7 +161,20 @@ class JoinRoomViewTest {
|
|||
),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_decline)
|
||||
eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite)
|
||||
eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Decline and block when JoinAuthorisationStatus is IsInvited emits the expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||
rule.setJoinRoomView(
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(null)),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(R.string.screen_join_room_decline_and_block_button_title)
|
||||
eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID
|
|||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -40,9 +41,9 @@ class BlockedUsersPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - initial state with blocked users`() = runTest {
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
ignoredUsersFlow.value = persistentListOf(A_USER_ID)
|
||||
}
|
||||
val matrixClient = FakeMatrixClient(
|
||||
ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID))
|
||||
)
|
||||
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -56,9 +57,10 @@ class BlockedUsersPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - blocked users list updates with new emissions`() = runTest {
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
ignoredUsersFlow.value = persistentListOf(A_USER_ID)
|
||||
}
|
||||
val ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID))
|
||||
val matrixClient = FakeMatrixClient(
|
||||
ignoredUsersFlow = ignoredUsersFlow
|
||||
)
|
||||
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -66,7 +68,7 @@ class BlockedUsersPresenterTest {
|
|||
with(awaitItem()) {
|
||||
assertThat(blockedUsers).isEqualTo(listOf(MatrixUser(A_USER_ID)))
|
||||
}
|
||||
matrixClient.ignoredUsersFlow.value = persistentListOf(A_USER_ID, A_USER_ID_2)
|
||||
ignoredUsersFlow.value = persistentListOf(A_USER_ID, A_USER_ID_2)
|
||||
skipItems(1)
|
||||
with(awaitItem()) {
|
||||
assertThat(blockedUsers).isEqualTo(listOf(MatrixUser(A_USER_ID), MatrixUser(A_USER_ID_2)))
|
||||
|
|
@ -77,8 +79,9 @@ class BlockedUsersPresenterTest {
|
|||
@Test
|
||||
fun `present - blocked users list with data`() = runTest {
|
||||
val alice = MatrixUser(A_USER_ID, displayName = "Alice", avatarUrl = "aliceAvatar")
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
ignoredUsersFlow.value = persistentListOf(A_USER_ID, A_USER_ID_2)
|
||||
val matrixClient = FakeMatrixClient(
|
||||
ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID, A_USER_ID_2))
|
||||
).apply {
|
||||
givenGetProfileResult(A_USER_ID, Result.success(alice))
|
||||
givenGetProfileResult(A_USER_ID_2, Result.failure(AN_EXCEPTION))
|
||||
}
|
||||
|
|
@ -103,9 +106,9 @@ class BlockedUsersPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - unblock user`() = runTest {
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
ignoredUsersFlow.value = persistentListOf(A_USER_ID)
|
||||
}
|
||||
val matrixClient = FakeMatrixClient(
|
||||
ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID))
|
||||
)
|
||||
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -125,10 +128,10 @@ class BlockedUsersPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - unblock user handles failure`() = runTest {
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
ignoredUsersFlow.value = persistentListOf(A_USER_ID)
|
||||
givenUnignoreUserResult(Result.failure(IllegalStateException("User not banned")))
|
||||
}
|
||||
val matrixClient = FakeMatrixClient(
|
||||
unIgnoreUserResult = { Result.failure(IllegalStateException("User not banned")) },
|
||||
ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID))
|
||||
)
|
||||
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -147,10 +150,10 @@ class BlockedUsersPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - unblock user then cancel`() = runTest {
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
ignoredUsersFlow.value = persistentListOf(A_USER_ID)
|
||||
givenUnignoreUserResult(Result.failure(IllegalStateException("User not banned")))
|
||||
}
|
||||
val matrixClient = FakeMatrixClient(
|
||||
unIgnoreUserResult = { Result.failure(IllegalStateException("User not banned")) },
|
||||
ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID))
|
||||
)
|
||||
val presenter = aBlockedUsersPresenter(matrixClient = matrixClient)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
|
|||
|
|
@ -329,9 +329,12 @@ class RoomListPresenter @Inject constructor(
|
|||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun RoomListRoomSummary.toInviteData() = InviteData(
|
||||
roomId = roomId,
|
||||
// Note: `name` should not be null at this point, but just in case, fallback to the roomId
|
||||
roomName = name ?: roomId.value,
|
||||
isDm = isDm,
|
||||
)
|
||||
internal fun RoomListRoomSummary.toInviteData(): InviteData? {
|
||||
if (inviteSender == null) return null
|
||||
return InviteData(
|
||||
roomId = roomId,
|
||||
roomName = name ?: roomId.value,
|
||||
isDm = isDm,
|
||||
senderId = inviteSender.userId,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,11 @@ import io.element.android.tests.testutils.lambda.any
|
|||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
|
@ -169,7 +173,8 @@ class UserProfilePresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - BlockUser and UnblockUser without confirmation change the 'blocked' state`() = runTest {
|
||||
val client = createFakeMatrixClient()
|
||||
val ignoredUsersFlow = MutableStateFlow(persistentListOf<UserId>())
|
||||
val client = createFakeMatrixClient(ignoredUsersFlow = ignoredUsersFlow)
|
||||
val presenter = createUserProfilePresenter(
|
||||
client = client,
|
||||
userId = A_USER_ID
|
||||
|
|
@ -178,20 +183,21 @@ class UserProfilePresenterTest {
|
|||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = false))
|
||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||
client.emitIgnoreUserList(listOf(A_USER_ID))
|
||||
ignoredUsersFlow.emit(persistentListOf(A_USER_ID))
|
||||
assertThat(awaitItem().isBlocked.dataOrNull()).isTrue()
|
||||
|
||||
initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = false))
|
||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||
client.emitIgnoreUserList(listOf())
|
||||
ignoredUsersFlow.emit(persistentListOf())
|
||||
assertThat(awaitItem().isBlocked.dataOrNull()).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - BlockUser with error`() = runTest {
|
||||
val matrixClient = createFakeMatrixClient()
|
||||
matrixClient.givenIgnoreUserResult(Result.failure(A_THROWABLE))
|
||||
val matrixClient = createFakeMatrixClient(
|
||||
ignoreUserResult = { Result.failure(A_THROWABLE) }
|
||||
)
|
||||
val presenter = createUserProfilePresenter(client = matrixClient)
|
||||
presenter.test {
|
||||
val initialState = awaitFirstItem()
|
||||
|
|
@ -207,8 +213,9 @@ class UserProfilePresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - UnblockUser with error`() = runTest {
|
||||
val matrixClient = createFakeMatrixClient()
|
||||
matrixClient.givenUnignoreUserResult(Result.failure(A_THROWABLE))
|
||||
val matrixClient = createFakeMatrixClient(
|
||||
unIgnoreUserResult = { Result.failure(A_THROWABLE) }
|
||||
)
|
||||
val presenter = createUserProfilePresenter(client = matrixClient)
|
||||
presenter.test {
|
||||
val initialState = awaitFirstItem()
|
||||
|
|
@ -374,10 +381,16 @@ class UserProfilePresenterTest {
|
|||
|
||||
private fun createFakeMatrixClient(
|
||||
isUserVerified: Boolean = false,
|
||||
ignoreUserResult: (UserId) -> Result<Unit> = { Result.success(Unit) },
|
||||
unIgnoreUserResult: (UserId) -> Result<Unit> = { Result.success(Unit) },
|
||||
ignoredUsersFlow: StateFlow<ImmutableList<UserId>> = MutableStateFlow(persistentListOf())
|
||||
) = FakeMatrixClient(
|
||||
encryptionService = FakeEncryptionService(
|
||||
isUserVerifiedResult = { Result.success(isUserVerified) }
|
||||
),
|
||||
ignoreUserResult = ignoreUserResult,
|
||||
unIgnoreUserResult = unIgnoreUserResult,
|
||||
ignoredUsersFlow = ignoredUsersFlow
|
||||
)
|
||||
|
||||
private fun createUserProfilePresenter(
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder
|
|||
import io.element.android.tests.testutils.simulateLongTask
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -85,7 +84,10 @@ class FakeMatrixClient(
|
|||
private val canDeactivateAccountResult: () -> Boolean = { lambdaError() },
|
||||
private val deactivateAccountResult: (String, Boolean) -> Result<Unit> = { _, _ -> lambdaError() },
|
||||
private val currentSlidingSyncVersionLambda: () -> Result<SlidingSyncVersion> = { lambdaError() },
|
||||
private val availableSlidingSyncVersionsLambda: () -> Result<List<SlidingSyncVersion>> = { lambdaError() }
|
||||
private val availableSlidingSyncVersionsLambda: () -> Result<List<SlidingSyncVersion>> = { lambdaError() },
|
||||
private val ignoreUserResult: (UserId) -> Result<Unit> = { lambdaError() },
|
||||
private var unIgnoreUserResult: (UserId) -> Result<Unit> = { Result.success(Unit) },
|
||||
override val ignoredUsersFlow: StateFlow<ImmutableList<UserId>> = MutableStateFlow(persistentListOf()),
|
||||
) : MatrixClient {
|
||||
var setDisplayNameCalled: Boolean = false
|
||||
private set
|
||||
|
|
@ -96,10 +98,7 @@ class FakeMatrixClient(
|
|||
|
||||
private val _userProfile: MutableStateFlow<MatrixUser> = MutableStateFlow(MatrixUser(sessionId, userDisplayName, userAvatarUrl))
|
||||
override val userProfile: StateFlow<MatrixUser> = _userProfile
|
||||
override val ignoredUsersFlow: MutableStateFlow<ImmutableList<UserId>> = MutableStateFlow(persistentListOf())
|
||||
|
||||
private var ignoreUserResult: Result<Unit> = Result.success(Unit)
|
||||
private var unignoreUserResult: Result<Unit> = Result.success(Unit)
|
||||
private var createRoomResult: Result<RoomId> = Result.success(A_ROOM_ID)
|
||||
private var createDmResult: Result<RoomId> = Result.success(A_ROOM_ID)
|
||||
private var findDmResult: RoomId? = A_ROOM_ID
|
||||
|
|
@ -137,11 +136,11 @@ class FakeMatrixClient(
|
|||
}
|
||||
|
||||
override suspend fun ignoreUser(userId: UserId): Result<Unit> = simulateLongTask {
|
||||
return ignoreUserResult
|
||||
return ignoreUserResult(userId)
|
||||
}
|
||||
|
||||
override suspend fun unignoreUser(userId: UserId): Result<Unit> = simulateLongTask {
|
||||
return unignoreUserResult
|
||||
return unIgnoreUserResult(userId)
|
||||
}
|
||||
|
||||
override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> = simulateLongTask {
|
||||
|
|
@ -239,10 +238,6 @@ class FakeMatrixClient(
|
|||
return RoomMembershipObserver()
|
||||
}
|
||||
|
||||
suspend fun emitIgnoreUserList(users: List<UserId>) {
|
||||
ignoredUsersFlow.emit(users.toImmutableList())
|
||||
}
|
||||
|
||||
// Mocks
|
||||
|
||||
fun givenCreateRoomResult(result: Result<RoomId>) {
|
||||
|
|
@ -253,14 +248,6 @@ class FakeMatrixClient(
|
|||
createDmResult = result
|
||||
}
|
||||
|
||||
fun givenIgnoreUserResult(result: Result<Unit>) {
|
||||
ignoreUserResult = result
|
||||
}
|
||||
|
||||
fun givenUnignoreUserResult(result: Result<Unit>) {
|
||||
unignoreUserResult = result
|
||||
}
|
||||
|
||||
fun givenFindDmResult(result: RoomId?) {
|
||||
findDmResult = result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3f5e0ac8b492e4152a0eb8ef9cb0b6456b6cd9dad9733a23c99ad53def2d15ff
|
||||
size 9962
|
||||
oid sha256:970efb76758b76905d04d159e123201bbcf34e25ca92ba49956f5ed4beb68d1e
|
||||
size 37097
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3f5e0ac8b492e4152a0eb8ef9cb0b6456b6cd9dad9733a23c99ad53def2d15ff
|
||||
size 9962
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9e05f1b1cfc75e0698e82c76985255d43838fc9e07dbfda39a5f7b27dcd7dcc8
|
||||
size 8596
|
||||
oid sha256:6b07a822f818e4709be8afe2e3a79a8aa03b42bb67ec3206cc310b5386037ab3
|
||||
size 34758
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9e05f1b1cfc75e0698e82c76985255d43838fc9e07dbfda39a5f7b27dcd7dcc8
|
||||
size 8596
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9a869730be3df8fdcbaa2bd251968b330a5e82733cfd6efa86e8a4971552a306
|
||||
size 26612
|
||||
oid sha256:2770e628229279569dd66e62395cce39e763ffdf64ddd9901e72afa3c7055c07
|
||||
size 29911
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e97676e6f90749d608c8289137c39668c122228f6660af9ed4b056f4f05d61ca
|
||||
size 34946
|
||||
oid sha256:8286e517a48da892830571538a9047d9d91daae01400ce0ff19673a932debf96
|
||||
size 37727
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7b16b479b55dcdc22e17be7d9a0996de4b7f1c0c708a013c134483369073b316
|
||||
size 26237
|
||||
oid sha256:25fc6358031c6f3b10401b2e8e5227ae0c63b36bd77e0d2f115eb19988f8cfbd
|
||||
size 29242
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d4a30364d8f94b77b7f518107c531f0fe2e1e9e16729777e8d84958e06b198af
|
||||
size 34270
|
||||
oid sha256:58178d173a6575ea5bdfce718f5d1686767f7078e19df2c18b55dca7ff6edb2f
|
||||
size 36944
|
||||
|
|
|
|||
|
|
@ -56,7 +56,8 @@
|
|||
{
|
||||
"name" : ":features:invite:impl",
|
||||
"includeRegex" : [
|
||||
"screen_invites_.*"
|
||||
"screen_invites_.*",
|
||||
"screen\\.join_room\\.decline_and_block_.*"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue