Merge pull request #3725 from element-hq/feature/fga/knock_request_to_join
Feature: knock request to join
This commit is contained in:
commit
52053fcf38
62 changed files with 758 additions and 339 deletions
|
|
@ -21,9 +21,9 @@ import io.element.android.libraries.matrix.api.notification.NotificationService
|
|||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
|
||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||
import io.element.android.libraries.matrix.api.room.InvitedRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.PendingRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||
|
|
@ -52,7 +52,7 @@ interface MatrixClient : Closeable {
|
|||
val sessionCoroutineScope: CoroutineScope
|
||||
val ignoredUsersFlow: StateFlow<ImmutableList<UserId>>
|
||||
suspend fun getRoom(roomId: RoomId): MatrixRoom?
|
||||
suspend fun getInvitedRoom(roomId: RoomId): InvitedRoom?
|
||||
suspend fun getPendingRoom(roomId: RoomId): PendingRoom?
|
||||
suspend fun findDM(userId: UserId): RoomId?
|
||||
suspend fun ignoreUser(userId: UserId): Result<Unit>
|
||||
suspend fun unignoreUser(userId: UserId): Result<Unit>
|
||||
|
|
@ -65,7 +65,7 @@ interface MatrixClient : Closeable {
|
|||
suspend fun removeAvatar(): Result<Unit>
|
||||
suspend fun joinRoom(roomId: RoomId): Result<RoomSummary?>
|
||||
suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomSummary?>
|
||||
suspend fun knockRoom(roomId: RoomId): Result<Unit>
|
||||
suspend fun knockRoom(roomIdOrAlias: RoomIdOrAlias, message: String, serverNames: List<String>): Result<RoomSummary?>
|
||||
fun syncService(): SyncService
|
||||
fun sessionVerificationService(): SessionVerificationService
|
||||
fun pushersService(): PushersService
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ package io.element.android.libraries.matrix.api.room
|
|||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
/** A reference to a room the current user has been invited to, with the ability to decline the invite. */
|
||||
interface InvitedRoom : AutoCloseable {
|
||||
/** A reference to a room the current user has knocked to or has been invited to, with the ability to leave the room. */
|
||||
interface PendingRoom : AutoCloseable {
|
||||
val sessionId: SessionId
|
||||
val roomId: RoomId
|
||||
|
||||
/** Decline the invite to this room. */
|
||||
suspend fun declineInvite(): Result<Unit>
|
||||
/** Leave the room ie.decline invite or cancel knock. */
|
||||
suspend fun leave(): Result<Unit>
|
||||
}
|
||||
|
|
@ -30,8 +30,8 @@ import io.element.android.libraries.matrix.api.notificationsettings.Notification
|
|||
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
|
||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.InvitedRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.PendingRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||
|
|
@ -251,24 +251,26 @@ class RustMatrixClient(
|
|||
return roomFactory.create(roomId)
|
||||
}
|
||||
|
||||
override suspend fun getInvitedRoom(roomId: RoomId): InvitedRoom? {
|
||||
return roomFactory.createInvitedRoom(roomId)
|
||||
override suspend fun getPendingRoom(roomId: RoomId): PendingRoom? {
|
||||
return roomFactory.createPendingRoom(roomId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the room to be available in the room list, with a membership for the current user of [CurrentUserMembership.JOINED].
|
||||
* Wait for the room to be available in the room list with the correct membership for the current user.
|
||||
* @param roomIdOrAlias the room id or alias to wait for
|
||||
* @param timeout the timeout to wait for the room to be available
|
||||
* @param currentUserMembership the membership to wait for
|
||||
* @throws TimeoutCancellationException if the room is not available after the timeout
|
||||
*/
|
||||
private suspend fun awaitJoinedRoom(
|
||||
private suspend fun awaitRoom(
|
||||
roomIdOrAlias: RoomIdOrAlias,
|
||||
timeout: Duration
|
||||
timeout: Duration,
|
||||
currentUserMembership: CurrentUserMembership,
|
||||
): RoomSummary {
|
||||
return withTimeout(timeout) {
|
||||
getRoomSummaryFlow(roomIdOrAlias)
|
||||
.mapNotNull { optionalRoomSummary -> optionalRoomSummary.getOrNull() }
|
||||
.filter { roomSummary -> roomSummary.info.currentUserMembership == CurrentUserMembership.JOINED }
|
||||
.filter { roomSummary -> roomSummary.info.currentUserMembership == currentUserMembership }
|
||||
.first()
|
||||
// Ensure that the room is ready
|
||||
.also { client.awaitRoomRemoteEcho(it.roomId.value) }
|
||||
|
|
@ -314,7 +316,7 @@ class RustMatrixClient(
|
|||
val roomId = RoomId(client.createRoom(rustParams))
|
||||
// Wait to receive the room back from the sync but do not returns failure if it fails.
|
||||
try {
|
||||
awaitJoinedRoom(roomId.toRoomIdOrAlias(), 30.seconds)
|
||||
awaitRoom(roomId.toRoomIdOrAlias(), 30.seconds, CurrentUserMembership.JOINED)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Timeout waiting for the room to be available in the room list")
|
||||
}
|
||||
|
|
@ -369,7 +371,7 @@ class RustMatrixClient(
|
|||
runCatching {
|
||||
client.joinRoomById(roomId.value).destroy()
|
||||
try {
|
||||
awaitJoinedRoom(roomId.toRoomIdOrAlias(), 10.seconds)
|
||||
awaitRoom(roomId.toRoomIdOrAlias(), 10.seconds, CurrentUserMembership.JOINED)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Timeout waiting for the room to be available in the room list")
|
||||
null
|
||||
|
|
@ -384,7 +386,7 @@ class RustMatrixClient(
|
|||
serverNames = serverNames,
|
||||
).destroy()
|
||||
try {
|
||||
awaitJoinedRoom(roomIdOrAlias, 10.seconds)
|
||||
awaitRoom(roomIdOrAlias, 10.seconds, CurrentUserMembership.JOINED)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Timeout waiting for the room to be available in the room list")
|
||||
null
|
||||
|
|
@ -392,8 +394,18 @@ class RustMatrixClient(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun knockRoom(roomId: RoomId): Result<Unit> {
|
||||
return Result.failure(NotImplementedError("Not yet implemented"))
|
||||
override suspend fun knockRoom(roomIdOrAlias: RoomIdOrAlias, message: String, serverNames: List<String>): Result<RoomSummary?> = withContext(
|
||||
sessionDispatcher
|
||||
) {
|
||||
runCatching {
|
||||
client.knock(roomIdOrAlias.identifier).destroy()
|
||||
try {
|
||||
awaitRoom(roomIdOrAlias, 10.seconds, CurrentUserMembership.KNOCKED)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Timeout waiting for the room to be available in the room list")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit> = withContext(sessionDispatcher) {
|
||||
|
|
|
|||
|
|
@ -9,20 +9,20 @@ package io.element.android.libraries.matrix.impl.room
|
|||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.room.InvitedRoom
|
||||
import io.element.android.libraries.matrix.api.room.PendingRoom
|
||||
import org.matrix.rustcomponents.sdk.Room
|
||||
|
||||
class RustInvitedRoom(
|
||||
class RustPendingRoom(
|
||||
override val sessionId: SessionId,
|
||||
private val invitedRoom: Room,
|
||||
) : InvitedRoom {
|
||||
override val roomId = RoomId(invitedRoom.id())
|
||||
private val inner: Room,
|
||||
) : PendingRoom {
|
||||
override val roomId = RoomId(inner.id())
|
||||
|
||||
override suspend fun declineInvite(): Result<Unit> = runCatching {
|
||||
invitedRoom.leave()
|
||||
override suspend fun leave(): Result<Unit> = runCatching {
|
||||
inner.leave()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
invitedRoom.destroy()
|
||||
inner.destroy()
|
||||
}
|
||||
}
|
||||
|
|
@ -14,8 +14,8 @@ import io.element.android.libraries.matrix.api.core.DeviceId
|
|||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.room.InvitedRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.PendingRoom
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.roomlist.awaitLoaded
|
||||
import io.element.android.libraries.matrix.impl.roomlist.fullRoomWithTimeline
|
||||
|
|
@ -35,6 +35,7 @@ import timber.log.Timber
|
|||
import org.matrix.rustcomponents.sdk.RoomListService as InnerRoomListService
|
||||
|
||||
private const val CACHE_SIZE = 16
|
||||
private val PENDING_MEMBERSHIPS = setOf(Membership.INVITED, Membership.KNOCKED)
|
||||
|
||||
class RustRoomFactory(
|
||||
private val sessionId: SessionId,
|
||||
|
|
@ -120,7 +121,7 @@ class RustRoomFactory(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun createInvitedRoom(roomId: RoomId): InvitedRoom? = withContext(dispatcher) {
|
||||
suspend fun createPendingRoom(roomId: RoomId): PendingRoom? = withContext(dispatcher) {
|
||||
if (isDestroyed) {
|
||||
Timber.d("Room factory is destroyed, returning null for $roomId")
|
||||
return@withContext null
|
||||
|
|
@ -130,20 +131,20 @@ class RustRoomFactory(
|
|||
Timber.d("Room not found for $roomId")
|
||||
return@withContext null
|
||||
}
|
||||
if (roomListItem.membership() != Membership.INVITED) {
|
||||
Timber.d("Room $roomId is not in invited state")
|
||||
if (roomListItem.membership() !in PENDING_MEMBERSHIPS) {
|
||||
Timber.d("Room $roomId is not in pending state")
|
||||
return@withContext null
|
||||
}
|
||||
val invitedRoom = try {
|
||||
val innerRoom = try {
|
||||
// TODO use new method when available, for now it'll fail for knocked rooms
|
||||
roomListItem.invitedRoom()
|
||||
} catch (e: RoomListException) {
|
||||
Timber.e(e, "Failed to get invited room for $roomId")
|
||||
Timber.e(e, "Failed to get pending room for $roomId")
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
RustInvitedRoom(
|
||||
RustPendingRoom(
|
||||
sessionId = sessionId,
|
||||
invitedRoom = invitedRoom,
|
||||
inner = innerRoom,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ import io.element.android.libraries.matrix.api.notification.NotificationService
|
|||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
|
||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||
import io.element.android.libraries.matrix.api.room.InvitedRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.PendingRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||
|
|
@ -101,7 +101,7 @@ class FakeMatrixClient(
|
|||
private var createDmResult: Result<RoomId> = Result.success(A_ROOM_ID)
|
||||
private var findDmResult: RoomId? = A_ROOM_ID
|
||||
private val getRoomResults = mutableMapOf<RoomId, MatrixRoom>()
|
||||
val getInvitedRoomResults = mutableMapOf<RoomId, InvitedRoom>()
|
||||
val getPendingRoomResults = mutableMapOf<RoomId, PendingRoom>()
|
||||
private val searchUserResults = mutableMapOf<String, Result<MatrixSearchUserResults>>()
|
||||
private val getProfileResults = mutableMapOf<UserId, Result<MatrixUser>>()
|
||||
private var uploadMediaResult: Result<String> = Result.success(AN_AVATAR_URL)
|
||||
|
|
@ -114,8 +114,8 @@ class FakeMatrixClient(
|
|||
var joinRoomByIdOrAliasLambda: (RoomIdOrAlias, List<String>) -> Result<RoomSummary?> = { _, _ ->
|
||||
Result.success(null)
|
||||
}
|
||||
var knockRoomLambda: (RoomId) -> Result<Unit> = {
|
||||
Result.success(Unit)
|
||||
var knockRoomLambda: (RoomIdOrAlias, String, List<String>) -> Result<RoomSummary?> = { _, _, _ ->
|
||||
Result.success(null)
|
||||
}
|
||||
var getRoomSummaryFlowLambda = { _: RoomIdOrAlias ->
|
||||
flowOf<Optional<RoomSummary>>(Optional.empty())
|
||||
|
|
@ -128,8 +128,8 @@ class FakeMatrixClient(
|
|||
return getRoomResults[roomId]
|
||||
}
|
||||
|
||||
override suspend fun getInvitedRoom(roomId: RoomId): InvitedRoom? {
|
||||
return getInvitedRoomResults[roomId]
|
||||
override suspend fun getPendingRoom(roomId: RoomId): PendingRoom? {
|
||||
return getPendingRoomResults[roomId]
|
||||
}
|
||||
|
||||
override suspend fun findDM(userId: UserId): RoomId? {
|
||||
|
|
@ -223,7 +223,9 @@ class FakeMatrixClient(
|
|||
return joinRoomByIdOrAliasLambda(roomIdOrAlias, serverNames)
|
||||
}
|
||||
|
||||
override suspend fun knockRoom(roomId: RoomId): Result<Unit> = knockRoomLambda(roomId)
|
||||
override suspend fun knockRoom(roomIdOrAlias: RoomIdOrAlias, message: String, serverNames: List<String>): Result<RoomSummary?> {
|
||||
return knockRoomLambda(roomIdOrAlias, message, serverNames)
|
||||
}
|
||||
|
||||
override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService
|
||||
|
||||
|
|
|
|||
|
|
@ -9,18 +9,18 @@ package io.element.android.libraries.matrix.test.room
|
|||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.room.InvitedRoom
|
||||
import io.element.android.libraries.matrix.api.room.PendingRoom
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.simulateLongTask
|
||||
|
||||
class FakeInvitedRoom(
|
||||
class FakePendingRoom(
|
||||
override val sessionId: SessionId = A_SESSION_ID,
|
||||
override val roomId: RoomId = A_ROOM_ID,
|
||||
private val declineInviteResult: () -> Result<Unit> = { lambdaError() }
|
||||
) : InvitedRoom {
|
||||
override suspend fun declineInvite(): Result<Unit> = simulateLongTask {
|
||||
) : PendingRoom {
|
||||
override suspend fun leave(): Result<Unit> = simulateLongTask {
|
||||
declineInviteResult()
|
||||
}
|
||||
|
||||
|
|
@ -8,10 +8,11 @@
|
|||
package io.element.android.libraries.matrix.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
|
|
@ -30,11 +31,12 @@ fun InviteSenderView(
|
|||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = modifier,
|
||||
) {
|
||||
Box(modifier = Modifier.padding(vertical = 2.dp)) {
|
||||
Avatar(avatarData = inviteSender.avatarData)
|
||||
}
|
||||
Text(
|
||||
text = inviteSender.annotatedString(),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue