Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: knock request to join #3725

Merged
merged 11 commits into from
Oct 23, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ class AcceptDeclineInvitePresenter @Inject constructor(

private fun CoroutineScope.declineInvite(roomId: RoomId, declinedAction: MutableState<AsyncAction<RoomId>>) = launch {
suspend {
client.getInvitedRoom(roomId)?.use {
it.declineInvite().getOrThrow()
client.getPendingRoom(roomId)?.use {
it.leave().getOrThrow()
notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, roomId)
}
roomId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ 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.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeInvitedRoom
import io.element.android.libraries.matrix.test.room.FakePendingRoom
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
import io.element.android.libraries.push.api.notifications.NotificationCleaner
import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner
Expand Down Expand Up @@ -78,7 +78,7 @@ class AcceptDeclineInvitePresenterTest {
Result.failure<Unit>(RuntimeException("Failed to leave room"))
}
val client = FakeMatrixClient().apply {
getInvitedRoomResults[A_ROOM_ID] = FakeInvitedRoom(declineInviteResult = declineInviteFailure)
getPendingRoomResults[A_ROOM_ID] = FakePendingRoom(declineInviteResult = declineInviteFailure)
}
val presenter = createAcceptDeclineInvitePresenter(client = client)
presenter.test {
Expand Down Expand Up @@ -121,7 +121,7 @@ class AcceptDeclineInvitePresenterTest {
Result.success(Unit)
}
val client = FakeMatrixClient().apply {
getInvitedRoomResults[A_ROOM_ID] = FakeInvitedRoom(declineInviteResult = declineInviteSuccess)
getPendingRoomResults[A_ROOM_ID] = FakePendingRoom(declineInviteResult = declineInviteSuccess)
}
val presenter = createAcceptDeclineInvitePresenter(
client = client,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ sealed interface JoinRoomEvents {
data object RetryFetchingContent : JoinRoomEvents
data object JoinRoom : JoinRoomEvents
data object KnockRoom : JoinRoomEvents
data object ClearError : JoinRoomEvents
data class CancelKnock(val requiresConfirmation: Boolean) : JoinRoomEvents
data class UpdateKnockMessage(val message: String) : JoinRoomEvents
data object ClearActionStates : JoinRoomEvents
data object AcceptInvite : JoinRoomEvents
data object DeclineInvite : JoinRoomEvents
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ class JoinRoomNode @AssistedInject constructor(
state = state,
onBackClick = ::navigateUp,
onJoinSuccess = ::navigateUp,
onKnockSuccess = ::navigateUp,
onCancelKnockSuccess = ::navigateUp,
onKnockSuccess = { },
modifier = modifier
)
acceptDeclineInviteView.Render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
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.InviteData
import io.element.android.features.joinroom.impl.di.CancelKnockRoom
import io.element.android.features.joinroom.impl.di.KnockRoom
import io.element.android.features.roomdirectory.api.RoomDescription
import io.element.android.libraries.architecture.AsyncAction
Expand All @@ -46,6 +48,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.util.Optional

private const val MAX_KNOCK_MESSAGE_LENGTH = 500

class JoinRoomPresenter @AssistedInject constructor(
@Assisted private val roomId: RoomId,
@Assisted private val roomIdOrAlias: RoomIdOrAlias,
Expand All @@ -55,6 +59,7 @@ class JoinRoomPresenter @AssistedInject constructor(
private val matrixClient: MatrixClient,
private val joinRoom: JoinRoom,
private val knockRoom: KnockRoom,
private val cancelKnockRoom: CancelKnockRoom,
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
private val buildMeta: BuildMeta,
) : Presenter<JoinRoomState> {
Expand All @@ -75,6 +80,8 @@ class JoinRoomPresenter @AssistedInject constructor(
val roomInfo by matrixClient.getRoomInfoFlow(roomId.toRoomIdOrAlias()).collectAsState(initial = Optional.empty())
val joinAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
val knockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
val cancelKnockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
var knockMessage by rememberSaveable { mutableStateOf("") }
val contentState by produceState<ContentState>(
initialValue = ContentState.Loading(roomIdOrAlias),
key1 = roomInfo,
Expand Down Expand Up @@ -110,7 +117,7 @@ class JoinRoomPresenter @AssistedInject constructor(
fun handleEvents(event: JoinRoomEvents) {
when (event) {
JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction)
JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction)
is JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction, knockMessage)
JoinRoomEvents.AcceptInvite -> {
val inviteData = contentState.toInviteData() ?: return
acceptDeclineInviteState.eventSink(
Expand All @@ -123,12 +130,17 @@ class JoinRoomPresenter @AssistedInject constructor(
AcceptDeclineInviteEvents.DeclineInvite(inviteData)
)
}
is JoinRoomEvents.CancelKnock -> coroutineScope.cancelKnockRoom(event.requiresConfirmation, cancelKnockAction)
JoinRoomEvents.RetryFetchingContent -> {
retryCount++
}
JoinRoomEvents.ClearError -> {
JoinRoomEvents.ClearActionStates -> {
knockAction.value = AsyncAction.Uninitialized
joinAction.value = AsyncAction.Uninitialized
cancelKnockAction.value = AsyncAction.Uninitialized
}
is JoinRoomEvents.UpdateKnockMessage -> {
knockMessage = event.message.take(MAX_KNOCK_MESSAGE_LENGTH)
}
}
}
Expand All @@ -138,7 +150,9 @@ class JoinRoomPresenter @AssistedInject constructor(
acceptDeclineInviteState = acceptDeclineInviteState,
joinAction = joinAction.value,
knockAction = knockAction.value,
cancelKnockAction = cancelKnockAction.value,
applicationName = buildMeta.applicationName,
knockMessage = knockMessage,
eventSink = ::handleEvents
)
}
Expand All @@ -153,9 +167,19 @@ class JoinRoomPresenter @AssistedInject constructor(
}
}

private fun CoroutineScope.knockRoom(knockAction: MutableState<AsyncAction<Unit>>) = launch {
private fun CoroutineScope.knockRoom(knockAction: MutableState<AsyncAction<Unit>>, message: String) = launch {
knockAction.runUpdatingState {
knockRoom(roomId)
knockRoom(roomIdOrAlias, message, serverNames)
}
}

private fun CoroutineScope.cancelKnockRoom(requiresConfirmation: Boolean, cancelKnockAction: MutableState<AsyncAction<Unit>>) = launch {
if (requiresConfirmation) {
cancelKnockAction.value = AsyncAction.ConfirmingNoParams
} else {
cancelKnockAction.runUpdatingState {
cancelKnockRoom(roomId)
}
}
}
}
Expand Down Expand Up @@ -206,14 +230,15 @@ internal fun MatrixRoomInfo.toContentState(): ContentState {
name = name,
topic = topic,
alias = canonicalAlias,
numberOfMembers = activeMembersCount.toLong(),
numberOfMembers = activeMembersCount,
isDm = isDm,
roomType = if (isSpace) RoomType.Space else RoomType.Room,
roomAvatarUrl = avatarUrl,
joinAuthorisationStatus = when {
currentUserMembership == CurrentUserMembership.INVITED -> JoinAuthorisationStatus.IsInvited(
inviteSender = inviter?.toInviteSender()
)
currentUserMembership == CurrentUserMembership.KNOCKED -> JoinAuthorisationStatus.IsKnocked
isPublic -> JoinAuthorisationStatus.CanJoin
else -> JoinAuthorisationStatus.Unknown
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ data class JoinRoomState(
val acceptDeclineInviteState: AcceptDeclineInviteState,
val joinAction: AsyncAction<Unit>,
val knockAction: AsyncAction<Unit>,
val cancelKnockAction: AsyncAction<Unit>,
val applicationName: String,
val knockMessage: String,
val eventSink: (JoinRoomEvents) -> Unit
) {
val joinAuthorisationStatus = when (contentState) {
Expand Down Expand Up @@ -68,6 +70,7 @@ sealed interface ContentState {

sealed interface JoinAuthorisationStatus {
data class IsInvited(val inviteSender: InviteSender?) : JoinAuthorisationStatus
data object IsKnocked : JoinAuthorisationStatus
data object CanKnock : JoinAuthorisationStatus
data object CanJoin : JoinAuthorisationStatus
data object Unknown : JoinAuthorisationStatus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
isDm = true,
)
),
aJoinRoomState(
contentState = aLoadedContentState(
name = "A knocked Room",
joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked
)
)
)
}

Expand Down Expand Up @@ -124,13 +130,17 @@ fun aJoinRoomState(
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
joinAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
knockAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
cancelKnockAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
knockMessage: String = "",
eventSink: (JoinRoomEvents) -> Unit = {}
) = JoinRoomState(
contentState = contentState,
acceptDeclineInviteState = acceptDeclineInviteState,
joinAction = joinAction,
knockAction = knockAction,
cancelKnockAction = cancelKnockAction,
applicationName = "AppName",
knockMessage = knockMessage,
eventSink = eventSink
)

Expand Down
Loading
Loading