diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 378ada5b16..76c9f902c7 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -180,6 +180,8 @@ interface MatrixRoom : Closeable { suspend fun canUserTriggerRoomNotification(userId: UserId): Result + suspend fun canUserPinUnpin(userId: UserId): Result + suspend fun canUserJoinCall(userId: UserId): Result = canUserSendState(userId, StateEventType.CALL_MEMBER) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt index 70da8e7364..c7de03dac6 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt @@ -17,6 +17,7 @@ package io.element.android.libraries.matrix.api.room import androidx.compose.runtime.Immutable +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId @@ -52,4 +53,5 @@ data class MatrixRoomInfo( val hasRoomCall: Boolean, val activeRoomCallParticipants: ImmutableList, val heroes: ImmutableList, + val pinnedEventIds: ImmutableList ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt index 3d8defa252..eb2fbe9a30 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt @@ -169,4 +169,23 @@ interface Timeline : AutoCloseable { ): Result suspend fun loadReplyDetails(eventId: EventId): InReplyTo + + /** + * Adds a new pinned event by sending an updated `m.room.pinned_events` + * event containing the new event id. + * + * Returns `true` if we sent the request, `false` if the event was already + * pinned. + */ + suspend fun pinEvent(eventId: EventId): Result + + /** + * Adds a new pinned event by sending an updated `m.room.pinned_events` + * event without the event id we want to remove. + * + * Returns `true` if we sent the request, `false` if the event wasn't + * pinned + */ + suspend fun unpinEvent(eventId: EventId): Result + } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt index 6a87b02a2b..a7461d6b0a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.impl.room +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId @@ -58,7 +59,8 @@ class MatrixRoomInfoMapper { userDefinedNotificationMode = it.userDefinedNotificationMode?.map(), hasRoomCall = it.hasRoomCall, activeRoomCallParticipants = it.activeRoomCallParticipants.toImmutableList(), - heroes = it.elementHeroes().toImmutableList() + heroes = it.elementHeroes().toImmutableList(), + pinnedEventIds = it.pinnedEventIds.map(::EventId).toImmutableList(), ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index b594bba5e4..96beb99d5a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -403,6 +403,12 @@ class RustMatrixRoom( } } + override suspend fun canUserPinUnpin(userId: UserId): Result { + return runCatching { + innerRoom.canUserPinUnpin(userId.value) + } + } + override suspend fun sendImage( file: File, thumbnailFile: File?, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index b57efc5603..87fca3d395 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -525,6 +525,18 @@ class RustTimeline( } } + override suspend fun pinEvent(eventId: EventId): Result = withContext(dispatcher) { + runCatching { + inner.pinEvent(eventId = eventId.value) + } + } + + override suspend fun unpinEvent(eventId: EventId): Result = withContext(dispatcher) { + runCatching { + inner.unpinEvent(eventId = eventId.value) + } + } + private suspend fun fetchDetailsForEvent(eventId: EventId): Result { return runCatching { inner.fetchDetailsForEvent(eventId.value) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index cfd0267516..e2be5bcb66 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -125,6 +125,7 @@ class FakeMatrixRoom( private val getWidgetDriverResult: (MatrixWidgetSettings) -> Result = { lambdaError() }, private val canUserTriggerRoomNotificationResult: (UserId) -> Result = { lambdaError() }, private val canUserJoinCallResult: (UserId) -> Result = { lambdaError() }, + private val canUserPinUnpinResult: (UserId) -> Result = { lambdaError() }, private val setIsFavoriteResult: (Boolean) -> Result = { lambdaError() }, private val powerLevelsResult: () -> Result = { lambdaError() }, private val updatePowerLevelsResult: () -> Result = { lambdaError() }, @@ -289,6 +290,10 @@ class FakeMatrixRoom( return canUserJoinCallResult(userId) } + override suspend fun canUserPinUnpin(userId: UserId): Result { + return canUserPinUnpinResult(userId) + } + override suspend fun sendImage( file: File, thumbnailFile: File?, @@ -517,6 +522,7 @@ fun aRoomInfo( userPowerLevels: ImmutableMap = persistentMapOf(), activeRoomCallParticipants: List = emptyList(), heroes: List = emptyList(), + pinnedEventIds: List = emptyList(), ) = MatrixRoomInfo( id = id, name = name, @@ -542,6 +548,7 @@ fun aRoomInfo( userPowerLevels = userPowerLevels, activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(), heroes = heroes.toImmutableList(), + pinnedEventIds = pinnedEventIds.toImmutableList(), ) fun defaultRoomPowerLevels() = MatrixRoomPowerLevels( diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt index 4fa9f7a94c..6216c7b182 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt @@ -33,6 +33,7 @@ import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler +import io.element.android.tests.testutils.lambda.lambdaError import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -371,6 +372,16 @@ class FakeTimeline( override suspend fun loadReplyDetails(eventId: EventId) = loadReplyDetailsLambda(eventId) + var pinEventLambda: (eventId: EventId) -> Result = lambdaError() + override suspend fun pinEvent(eventId: EventId): Result { + return pinEventLambda(eventId) + } + + var unpinEventLambda: (eventId: EventId) -> Result = lambdaError() + override suspend fun unpinEvent(eventId: EventId): Result { + return unpinEventLambda(eventId) + } + var closeCounter = 0 private set