Merge pull request #353 from vector-im/feature/fga/some_room_related_fixes
Feature/fga/some room related fixes
This commit is contained in:
commit
ca6a47edcd
67 changed files with 674 additions and 392 deletions
|
|
@ -47,7 +47,9 @@ suspend fun <T> (suspend () -> T).execute(state: MutableState<Async<T>>, errorMa
|
|||
}
|
||||
|
||||
suspend fun <T> (suspend () -> Result<T>).executeResult(state: MutableState<Async<T>>) {
|
||||
state.value = Async.Loading()
|
||||
if (state.value !is Async.Success) {
|
||||
state.value = Async.Loading()
|
||||
}
|
||||
this().fold(
|
||||
onSuccess = {
|
||||
state.value = Async.Success(it)
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ private fun InitialsAvatar(
|
|||
) {
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = avatarData.getInitial(),
|
||||
text = avatarData.initial,
|
||||
fontSize = (avatarData.size.dp / 2).value.sp,
|
||||
color = Color.White,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -30,8 +30,37 @@ data class AvatarData(
|
|||
@IgnoredOnParcel
|
||||
val size: AvatarSize = AvatarSize.MEDIUM
|
||||
) : Parcelable {
|
||||
fun getInitial(): String {
|
||||
val firstChar = name?.firstOrNull() ?: id.getOrNull(1) ?: '?'
|
||||
return firstChar.uppercase()
|
||||
|
||||
@IgnoredOnParcel
|
||||
val initial by lazy {
|
||||
(name?.takeIf { it.isNotBlank() } ?: id)
|
||||
.let { dn ->
|
||||
var startIndex = 0
|
||||
val initial = dn[startIndex]
|
||||
|
||||
if (initial in listOf('@', '#', '+') && dn.length > 1) {
|
||||
startIndex++
|
||||
}
|
||||
|
||||
var length = 1
|
||||
var first = dn[startIndex]
|
||||
|
||||
// LEFT-TO-RIGHT MARK
|
||||
if (dn.length >= 2 && 0x200e == first.code) {
|
||||
startIndex++
|
||||
first = dn[startIndex]
|
||||
}
|
||||
|
||||
// check if it’s the start of a surrogate pair
|
||||
if (first.code in 0xD800..0xDBFF && dn.length > startIndex + 1) {
|
||||
val second = dn[startIndex + 1]
|
||||
if (second.code in 0xDC00..0xDFFF) {
|
||||
length++
|
||||
}
|
||||
}
|
||||
|
||||
dn.substring(startIndex, startIndex + length)
|
||||
}
|
||||
.uppercase()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@ package io.element.android.libraries.designsystem.theme.components
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.ModalBottomSheetDefaults
|
||||
import androidx.compose.material.ModalBottomSheetState
|
||||
import androidx.compose.material.ModalBottomSheetValue
|
||||
import androidx.compose.material.contentColorFor
|
||||
import androidx.compose.material.rememberModalBottomSheetState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
|
@ -44,7 +44,7 @@ fun ModalBottomSheetLayout(
|
|||
sheetState: ModalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden),
|
||||
sheetShape: Shape = MaterialTheme.shapes.large,
|
||||
sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
|
||||
sheetBackgroundColor: Color = MaterialTheme.colors.surface,
|
||||
sheetBackgroundColor: Color = MaterialTheme.colorScheme.surface,
|
||||
sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
|
||||
scrimColor: Color = ModalBottomSheetDefaults.scrimColor,
|
||||
content: @Composable () -> Unit = {}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ interface MatrixClient : Closeable {
|
|||
val invitesDataSource: RoomSummaryDataSource
|
||||
fun getRoom(roomId: RoomId): MatrixRoom?
|
||||
fun findDM(userId: UserId): MatrixRoom?
|
||||
suspend fun ignoreUser(userId: UserId): Result<Unit>
|
||||
suspend fun unignoreUser(userId: UserId): Result<Unit>
|
||||
suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId>
|
||||
suspend fun createDM(userId: UserId): Result<RoomId>
|
||||
fun startSync()
|
||||
|
|
|
|||
|
|
@ -18,12 +18,15 @@ package io.element.android.libraries.matrix.api.room
|
|||
|
||||
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.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.io.Closeable
|
||||
|
||||
interface MatrixRoom : Closeable {
|
||||
val sessionId: SessionId
|
||||
val roomId: RoomId
|
||||
val name: String?
|
||||
val bestName: String
|
||||
|
|
@ -36,20 +39,22 @@ interface MatrixRoom : Closeable {
|
|||
val isDirect: Boolean
|
||||
val isPublic: Boolean
|
||||
|
||||
fun members() : Flow<List<RoomMember>>
|
||||
/**
|
||||
* The current loaded members as a StateFlow.
|
||||
* Initial value is [MatrixRoomMembersState.Unknown].
|
||||
* To update them you should call [updateMembers].
|
||||
*/
|
||||
val membersStateFlow: StateFlow<MatrixRoomMembersState>
|
||||
|
||||
fun updateMembers()
|
||||
|
||||
fun getMember(userId: UserId): Flow<RoomMember?>
|
||||
|
||||
fun getDmMember(): Flow<RoomMember?>
|
||||
/**
|
||||
* Try to load the room members and update the membersFlow.
|
||||
*/
|
||||
suspend fun updateMembers(): Result<Unit>
|
||||
|
||||
fun syncUpdateFlow(): Flow<Long>
|
||||
|
||||
fun timeline(): MatrixTimeline
|
||||
|
||||
suspend fun fetchMembers(): Result<Unit>
|
||||
|
||||
suspend fun userDisplayName(userId: UserId): Result<String?>
|
||||
|
||||
suspend fun userAvatarUrl(userId: UserId): Result<String?>
|
||||
|
|
@ -62,10 +67,6 @@ 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>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.matrix.api.room
|
||||
|
||||
sealed interface MatrixRoomMembersState {
|
||||
object Unknown : MatrixRoomMembersState
|
||||
data class Pending(val prevRoomMembers: List<RoomMember>? = null) : MatrixRoomMembersState
|
||||
data class Error(val failure: Throwable, val prevRoomMembers: List<RoomMember>? = null) : MatrixRoomMembersState
|
||||
data class Ready(val roomMembers: List<RoomMember>) : MatrixRoomMembersState
|
||||
}
|
||||
|
||||
fun MatrixRoomMembersState.roomMembers(): List<RoomMember>? {
|
||||
return when (this) {
|
||||
is MatrixRoomMembersState.Ready -> roomMembers
|
||||
is MatrixRoomMembersState.Pending -> prevRoomMembers
|
||||
is MatrixRoomMembersState.Error -> prevRoomMembers
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -16,11 +16,8 @@
|
|||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class RoomMember(
|
||||
val userId: UserId,
|
||||
val displayName: String?,
|
||||
|
|
@ -30,7 +27,7 @@ data class RoomMember(
|
|||
val powerLevel: Long,
|
||||
val normalizedPowerLevel: Long,
|
||||
val isIgnored: Boolean,
|
||||
) : Parcelable
|
||||
)
|
||||
|
||||
enum class RoomMembershipState {
|
||||
BAN, INVITE, JOIN, KNOCK, LEAVE
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ class RustMatrixClient constructor(
|
|||
val slidingSyncRoom = slidingSync.getRoom(roomId.value) ?: return null
|
||||
val fullRoom = slidingSyncRoom.fullRoom() ?: return null
|
||||
return RustMatrixRoom(
|
||||
currentUserId = sessionId,
|
||||
sessionId = sessionId,
|
||||
slidingSyncUpdateFlow = slidingSyncObserverProxy.updateSummaryFlow,
|
||||
slidingSyncRoom = slidingSyncRoom,
|
||||
innerRoom = fullRoom,
|
||||
|
|
@ -212,6 +212,18 @@ class RustMatrixClient constructor(
|
|||
return roomId?.let { getRoom(it) }
|
||||
}
|
||||
|
||||
override suspend fun ignoreUser(userId: UserId): Result<Unit> = withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
client.ignoreUser(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unignoreUser(userId: UserId): Result<Unit> = withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
client.unignoreUser(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> = withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
val rustParams = RustCreateRoomParameters(
|
||||
|
|
|
|||
|
|
@ -17,17 +17,19 @@
|
|||
package io.element.android.libraries.matrix.impl.room
|
||||
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
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.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||
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.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
|
|
@ -38,10 +40,9 @@ 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,
|
||||
override val sessionId: SessionId,
|
||||
private val slidingSyncUpdateFlow: Flow<UpdateSummary>,
|
||||
private val slidingSyncRoom: SlidingSyncRoom,
|
||||
private val innerRoom: Room,
|
||||
|
|
@ -49,6 +50,11 @@ class RustMatrixRoom(
|
|||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
) : MatrixRoom {
|
||||
|
||||
override val membersStateFlow: StateFlow<MatrixRoomMembersState>
|
||||
get() = _membersStateFlow
|
||||
|
||||
private var _membersStateFlow = MutableStateFlow<MatrixRoomMembersState>(MatrixRoomMembersState.Unknown)
|
||||
|
||||
private val timeline by lazy {
|
||||
RustMatrixTimeline(
|
||||
matrixRoom = this,
|
||||
|
|
@ -59,33 +65,6 @@ class RustMatrixRoom(
|
|||
)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun syncUpdateFlow(): Flow<Long> {
|
||||
return slidingSyncUpdateFlow
|
||||
.filter {
|
||||
|
|
@ -148,9 +127,16 @@ class RustMatrixRoom(
|
|||
override val isDirect: Boolean
|
||||
get() = innerRoom.isDirect()
|
||||
|
||||
override suspend fun fetchMembers(): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||
override suspend fun updateMembers(): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||
val currentState = _membersStateFlow.value
|
||||
val currentMembers = currentState.roomMembers()
|
||||
_membersStateFlow.value = MatrixRoomMembersState.Pending(prevRoomMembers = currentMembers)
|
||||
runCatching {
|
||||
innerRoom.fetchMembers()
|
||||
innerRoom.members().map(RoomMemberMapper::map)
|
||||
}.map {
|
||||
_membersStateFlow.value = MatrixRoomMembersState.Ready(it)
|
||||
}.onFailure {
|
||||
_membersStateFlow.value = MatrixRoomMembersState.Error(prevRoomMembers = currentMembers, failure = it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -206,31 +192,15 @@ class RustMatrixRoom(
|
|||
}
|
||||
|
||||
override suspend fun acceptInvitation(): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||
kotlin.runCatching {
|
||||
runCatching {
|
||||
innerRoom.acceptInvitation()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun rejectInvitation(): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||
kotlin.runCatching {
|
||||
runCatching {
|
||||
innerRoom.rejectInvitation()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import org.matrix.rustcomponents.sdk.SlidingSyncRoom
|
|||
import org.matrix.rustcomponents.sdk.TimelineItem
|
||||
import org.matrix.rustcomponents.sdk.TimelineListener
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class RustMatrixTimeline(
|
||||
private val matrixRoom: MatrixRoom,
|
||||
|
|
@ -51,6 +52,8 @@ class RustMatrixTimeline(
|
|||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
) : MatrixTimeline {
|
||||
|
||||
private val isInit = AtomicBoolean(false)
|
||||
|
||||
private val timelineItems: MutableStateFlow<List<MatrixTimelineItem>> =
|
||||
MutableStateFlow(emptyList())
|
||||
|
||||
|
|
@ -95,6 +98,7 @@ class RustMatrixTimeline(
|
|||
withContext(coroutineDispatchers.diffUpdateDispatcher) {
|
||||
this@RustMatrixTimeline.timelineItems.value = matrixTimelineItems
|
||||
}
|
||||
isInit.set(true)
|
||||
}
|
||||
.onFailure {
|
||||
Timber.e("Failed adding timeline listener on room with identifier: ${matrixRoom.roomId})")
|
||||
|
|
@ -105,6 +109,7 @@ class RustMatrixTimeline(
|
|||
override fun dispose() {
|
||||
Timber.v("Dispose timeline for room ${matrixRoom.roomId}")
|
||||
listenerTokens.dispose()
|
||||
isInit.set(false)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -125,6 +130,9 @@ class RustMatrixTimeline(
|
|||
override suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||
runCatching {
|
||||
Timber.v("Start back paginating for room ${matrixRoom.roomId} ")
|
||||
if (!isInit.get()) {
|
||||
throw IllegalStateException("Timeline is not init yet")
|
||||
}
|
||||
val paginationOptions = PaginationOptions.UntilNumItems(
|
||||
eventLimit = requestSize.toUShort(),
|
||||
items = untilNumberOfItems.toUShort()
|
||||
|
|
@ -141,15 +149,24 @@ class RustMatrixTimeline(
|
|||
runCatching {
|
||||
val settings = RoomSubscription(
|
||||
requiredState = listOf(
|
||||
RequiredState(key = "m.room.topic", value = ""),
|
||||
RequiredState(key = "m.room.canonical_alias", value = ""),
|
||||
RequiredState(key = "m.room.topic", value = ""),
|
||||
RequiredState(key = "m.room.join_rules", value = ""),
|
||||
),
|
||||
timelineLimit = null
|
||||
)
|
||||
val result = slidingSyncRoom.subscribeAndAddTimelineListener(timelineListener, settings)
|
||||
launch {
|
||||
fetchMembers()
|
||||
}
|
||||
listenerTokens += result.taskHandle
|
||||
result.items
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchMembers() = withContext(coroutineDispatchers.io) {
|
||||
runCatching {
|
||||
innerRoom.fetchMembers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ class FakeMatrixClient(
|
|||
private val notificationService: FakeNotificationService = FakeNotificationService(),
|
||||
) : MatrixClient {
|
||||
|
||||
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 createDmFailure: Throwable? = null
|
||||
|
|
@ -62,6 +64,14 @@ class FakeMatrixClient(
|
|||
return findDmResult
|
||||
}
|
||||
|
||||
override suspend fun ignoreUser(userId: UserId): Result<Unit> {
|
||||
return ignoreUserResult
|
||||
}
|
||||
|
||||
override suspend fun unignoreUser(userId: UserId): Result<Unit> {
|
||||
return unignoreUserResult
|
||||
}
|
||||
|
||||
override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> {
|
||||
delay(100)
|
||||
return createRoomResult
|
||||
|
|
@ -130,6 +140,14 @@ class FakeMatrixClient(
|
|||
createDmResult = result
|
||||
}
|
||||
|
||||
fun givenIgnoreUserResult(result: Result<Unit>) {
|
||||
ignoreUserResult = result
|
||||
}
|
||||
|
||||
fun givenUnignoreUserResult(result: Result<Unit>) {
|
||||
unignoreUserResult = result
|
||||
}
|
||||
|
||||
fun givenCreateDmError(failure: Throwable?) {
|
||||
createDmFailure = failure
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,21 +16,23 @@
|
|||
|
||||
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.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
class FakeMatrixRoom(
|
||||
override val sessionId: SessionId = A_SESSION_ID,
|
||||
override val roomId: RoomId = A_ROOM_ID,
|
||||
override val name: String? = null,
|
||||
override val bestName: String = "",
|
||||
|
|
@ -42,21 +44,16 @@ class FakeMatrixRoom(
|
|||
override val alternativeAliases: List<String> = emptyList(),
|
||||
override val isPublic: Boolean = true,
|
||||
override val isDirect: Boolean = false,
|
||||
private val members: List<RoomMember> = emptyList(),
|
||||
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
|
||||
) : MatrixRoom {
|
||||
|
||||
private var ignoreResult: Result<Unit> = Result.success(Unit)
|
||||
private var unignoreResult: Result<Unit> = Result.success(Unit)
|
||||
private var userDisplayNameResult = Result.success<String?>(null)
|
||||
private var userAvatarUrlResult = Result.success<String?>(null)
|
||||
private var updateMembersResult: Result<Unit> = Result.success(Unit)
|
||||
private var acceptInviteResult = Result.success(Unit)
|
||||
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
|
||||
|
||||
var isInviteAccepted: Boolean = false
|
||||
private set
|
||||
|
|
@ -66,6 +63,12 @@ class FakeMatrixRoom(
|
|||
|
||||
private var leaveRoomError: Throwable? = null
|
||||
|
||||
override val membersStateFlow: MutableStateFlow<MatrixRoomMembersState> = MutableStateFlow(MatrixRoomMembersState.Unknown)
|
||||
|
||||
override suspend fun updateMembers(): Result<Unit> {
|
||||
return updateMembersResult
|
||||
}
|
||||
|
||||
override fun syncUpdateFlow(): Flow<Long> {
|
||||
return emptyFlow()
|
||||
}
|
||||
|
|
@ -74,18 +77,6 @@ class FakeMatrixRoom(
|
|||
return matrixTimeline
|
||||
}
|
||||
|
||||
override suspend fun fetchMembers(): Result<Unit> {
|
||||
return fetchMemberResult.also { result ->
|
||||
if (result.isSuccess) {
|
||||
areMembersFetched = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDmMember(): Flow<RoomMember?> {
|
||||
return flowOf(dmMember)
|
||||
}
|
||||
|
||||
override suspend fun userDisplayName(userId: UserId): Result<String?> {
|
||||
return userDisplayNameResult
|
||||
}
|
||||
|
|
@ -94,20 +85,6 @@ class FakeMatrixRoom(
|
|||
return userAvatarUrlResult
|
||||
}
|
||||
|
||||
override fun members(): Flow<List<RoomMember>> {
|
||||
return fetchMemberResult.fold(onSuccess = {
|
||||
flowOf(members)
|
||||
}, onFailure = {
|
||||
errorFlow(it)
|
||||
})
|
||||
}
|
||||
|
||||
override fun updateMembers() = Unit
|
||||
|
||||
override fun getMember(userId: UserId): Flow<RoomMember?> {
|
||||
return flowOf(members.find { it.userId == userId })
|
||||
}
|
||||
|
||||
override suspend fun sendMessage(message: String): Result<Unit> {
|
||||
delay(100)
|
||||
return Result.success(Unit)
|
||||
|
|
@ -140,10 +117,6 @@ 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
|
||||
|
|
@ -161,12 +134,12 @@ class FakeMatrixRoom(
|
|||
this.leaveRoomError = throwable
|
||||
}
|
||||
|
||||
fun givenFetchMemberResult(result: Result<Unit>) {
|
||||
fetchMemberResult = result
|
||||
fun givenRoomMembersState(state: MatrixRoomMembersState) {
|
||||
membersStateFlow.value = state
|
||||
}
|
||||
|
||||
fun givenDmMember(roomMember: RoomMember) {
|
||||
this.dmMember = roomMember
|
||||
fun givenUpdateMembersResult(result: Result<Unit>) {
|
||||
updateMembersResult = result
|
||||
}
|
||||
|
||||
fun givenUserDisplayNameResult(displayName: Result<String?>) {
|
||||
|
|
@ -185,7 +158,6 @@ class FakeMatrixRoom(
|
|||
rejectInviteResult = result
|
||||
}
|
||||
|
||||
|
||||
fun givenIgnoreResult(result: Result<Unit>) {
|
||||
ignoreResult = result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,4 +33,7 @@ internal class MediaKeyer : Keyer<MediaResolver.Meta> {
|
|||
}
|
||||
}
|
||||
|
||||
private fun MediaResolver.Meta.toKey() = "${url}_${kind}"
|
||||
private fun MediaResolver.Meta.toKey(): String? {
|
||||
if (url.isNullOrBlank()) return null
|
||||
return "${url}_${kind}"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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.matrix.ui.room
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||
|
||||
@Composable
|
||||
fun MatrixRoom.getRoomMember(userId: UserId): State<RoomMember?> {
|
||||
val roomMembersState by membersStateFlow.collectAsState()
|
||||
return getRoomMember(roomMembersState = roomMembersState, userId = userId)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getRoomMember(roomMembersState: MatrixRoomMembersState, userId: UserId): State<RoomMember?> {
|
||||
val roomMembers = roomMembersState.roomMembers()
|
||||
return remember(roomMembers) {
|
||||
derivedStateOf {
|
||||
roomMembers?.find {
|
||||
it.userId == userId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MatrixRoom.getDirectRoomMember(): State<RoomMember?> {
|
||||
val roomMembersState by membersStateFlow.collectAsState()
|
||||
return getDirectRoomMember(roomMembersState = roomMembersState)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MatrixRoom.getDirectRoomMember(roomMembersState: MatrixRoomMembersState): State<RoomMember?> {
|
||||
val roomMembers = roomMembersState.roomMembers()
|
||||
return remember(roomMembers) {
|
||||
derivedStateOf {
|
||||
if (roomMembers == null) {
|
||||
null
|
||||
} else if (roomMembers.size == 2 && isDirect && isEncrypted) {
|
||||
roomMembers.find { it.userId != this.sessionId }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue