[Room Details] Block & unblock user (#340)

This commit is contained in:
Jorge Martin Espinosa 2023-04-26 16:14:44 +02:00 committed by GitHub
parent 0d327cd543
commit aef2b38983
35 changed files with 477 additions and 174 deletions

View file

@ -0,0 +1,22 @@
/*
* 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.libraries.core.coroutine
import kotlinx.coroutines.flow.flow
/** Create a Flow emitting a single error event. It should be useful for tests. */
fun <T> errorFlow(throwable: Throwable) = flow<T> { throw throwable }

View file

@ -36,13 +36,13 @@ interface MatrixRoom : Closeable {
val isDirect: Boolean
val isPublic: Boolean
suspend fun members(): List<RoomMember>
fun members() : Flow<List<RoomMember>>
suspend fun memberCount(): Int
fun updateMembers()
fun getMember(userId: UserId): RoomMember?
fun getMember(userId: UserId): Flow<RoomMember?>
fun getDmMember(): RoomMember?
fun getDmMember(): Flow<RoomMember?>
fun syncUpdateFlow(): Flow<Long>
@ -62,6 +62,10 @@ interface MatrixRoom : Closeable {
suspend fun redactEvent(eventId: EventId, reason: String? = null): Result<Unit>
suspend fun ignoreUser(userId: UserId): Result<Unit>
suspend fun unignoreUser(userId: UserId): Result<Unit>
suspend fun leave(): Result<Unit>
suspend fun acceptInvitation(): Result<Unit>

View file

@ -26,18 +26,19 @@ import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.onSubscription
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.SlidingSyncRoom
import org.matrix.rustcomponents.sdk.UpdateSummary
import org.matrix.rustcomponents.sdk.genTransactionId
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import org.matrix.rustcomponents.sdk.RoomMember as RustRoomMember
class RustMatrixRoom(
private val currentUserId: UserId,
@ -48,37 +49,40 @@ class RustMatrixRoom(
private val coroutineDispatchers: CoroutineDispatchers,
) : MatrixRoom {
private var loadMembersJob: Job? = null
private var cachedMembers: List<RoomMember> = emptyList()
private val timeline by lazy {
RustMatrixTimeline(
matrixRoom = this,
innerRoom = innerRoom,
slidingSyncRoom = slidingSyncRoom,
coroutineScope = coroutineScope,
coroutineDispatchers = coroutineDispatchers
)
}
override suspend fun members(): List<RoomMember> {
return cachedMembers.ifEmpty {
if (loadMembersJob == null) {
loadMembersJob = coroutineScope.launch(coroutineDispatchers.io) {
cachedMembers = tryOrNull {
innerRoom.members().map(RoomMemberMapper::map)
} ?: emptyList()
}
private var membersFlow = MutableStateFlow<List<RoomMember>>(emptyList())
override fun members(): Flow<List<RoomMember>> {
return membersFlow.onSubscription { updateMembers() }
}
override fun updateMembers() {
val updatedMembers = tryOrNull {
innerRoom.members().map(RoomMemberMapper::map)
} ?: emptyList()
membersFlow.tryEmit(updatedMembers)
}
override fun getMember(userId: UserId): Flow<RoomMember?> {
return membersFlow.map { members -> members.find { it.userId == userId } }
}
override fun getDmMember(): Flow<RoomMember?> {
return membersFlow.map { members ->
if (members.size == 2 && isDirect && isEncrypted) {
members.find { it.userId != currentUserId }
} else {
null
}
loadMembersJob?.join()
loadMembersJob = null
cachedMembers
}
}
override suspend fun memberCount(): Int {
return members().size
}
override fun getMember(userId: UserId): RoomMember? {
return cachedMembers.find { it.userId == userId }
}
override fun getDmMember(): RoomMember? {
return if (cachedMembers.size == 2 && isDirect && isEncrypted) {
cachedMembers.find { it.userId != currentUserId }
} else {
null
}
}
@ -94,13 +98,7 @@ class RustMatrixRoom(
}
override fun timeline(): MatrixTimeline {
return RustMatrixTimeline(
matrixRoom = this,
innerRoom = innerRoom,
slidingSyncRoom = slidingSyncRoom,
coroutineScope = coroutineScope,
coroutineDispatchers = coroutineDispatchers
)
return timeline
}
override fun close() {
@ -219,4 +217,20 @@ class RustMatrixRoom(
}
}
override suspend fun ignoreUser(userId: UserId): Result<Unit> {
return runCatching {
getRustMember(userId)?.ignore() ?: error("No member with userId $userId exists in room $roomId")
}
}
override suspend fun unignoreUser(userId: UserId): Result<Unit> {
return runCatching {
getRustMember(userId)?.unignore() ?: error("No member with userId $userId exists in room $roomId")
}
}
private fun getRustMember(userId: UserId): RustRoomMember? {
return innerRoom.members().find { it.userId() == userId.value }
}
}

View file

@ -139,7 +139,14 @@ class RustMatrixTimeline(
private suspend fun addListener(timelineListener: TimelineListener): Result<List<TimelineItem>> = withContext(coroutineDispatchers.io) {
runCatching {
val settings = RoomSubscription(requiredState = listOf(RequiredState(key = "m.room.canonical_alias", value = "")), timelineLimit = null)
val settings = RoomSubscription(
requiredState = listOf(
RequiredState(key = "m.room.topic", value = ""),
RequiredState(key = "m.room.canonical_alias", value = ""),
RequiredState(key = "m.room.join_rules", value = ""),
),
timelineLimit = null
)
val result = slidingSyncRoom.subscribeAndAddTimelineListener(timelineListener, settings)
listenerTokens += result.taskHandle
result.items

View file

@ -23,6 +23,7 @@ android {
}
dependencies {
api(projects.libraries.core)
api(projects.libraries.matrix.api)
api(libs.coroutines.core)
}

View file

@ -16,6 +16,7 @@
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.core.coroutine.errorFlow
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
@ -27,6 +28,7 @@ import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
class FakeMatrixRoom(
override val roomId: RoomId = A_ROOM_ID,
@ -50,6 +52,8 @@ class FakeMatrixRoom(
private var rejectInviteResult = Result.success(Unit)
private var dmMember: RoomMember? = null
private var fetchMemberResult: Result<Unit> = Result.success(Unit)
private var ignoreResult = Result.success(Unit)
private var unignoreResult = Result.success(Unit)
var areMembersFetched: Boolean = false
private set
@ -78,8 +82,8 @@ class FakeMatrixRoom(
}
}
override fun getDmMember(): RoomMember? {
return dmMember
override fun getDmMember(): Flow<RoomMember?> {
return flowOf(dmMember)
}
override suspend fun userDisplayName(userId: UserId): Result<String?> {
@ -90,20 +94,18 @@ class FakeMatrixRoom(
return userAvatarUrlResult
}
override suspend fun members(): List<RoomMember> {
return members
override fun members(): Flow<List<RoomMember>> {
return fetchMemberResult.fold(onSuccess = {
flowOf(members)
}, onFailure = {
errorFlow(it)
})
}
override suspend fun memberCount(): Int {
if (fetchMemberResult.isSuccess) {
return members.count()
} else {
throw fetchMemberResult.exceptionOrNull()!!
}
}
override fun updateMembers() = Unit
override fun getMember(userId: UserId): RoomMember? {
return members.firstOrNull { it.userId == userId }
override fun getMember(userId: UserId): Flow<RoomMember?> {
return flowOf(members.find { it.userId == userId })
}
override suspend fun sendMessage(message: String): Result<Unit> {
@ -138,6 +140,10 @@ class FakeMatrixRoom(
return Result.success(Unit)
}
override suspend fun ignoreUser(userId: UserId): Result<Unit> = ignoreResult
override suspend fun unignoreUser(userId: UserId): Result<Unit> = unignoreResult
override suspend fun leave(): Result<Unit> = leaveRoomError?.let { Result.failure(it) } ?: Result.success(Unit)
override suspend fun acceptInvitation(): Result<Unit> {
isInviteAccepted = true
@ -179,4 +185,12 @@ class FakeMatrixRoom(
rejectInviteResult = result
}
fun givenIgnoreResult(result: Result<Unit>) {
ignoreResult = result
}
fun givenUnIgnoreResult(result: Result<Unit>) {
unignoreResult = result
}
}