Merge branch 'develop' into feature/valere/message_shields
This commit is contained in:
commit
5d10b1fe85
342 changed files with 5475 additions and 1377 deletions
|
|
@ -106,6 +106,11 @@ interface MatrixRoom : Closeable {
|
|||
*/
|
||||
suspend fun timelineFocusedOnEvent(eventId: EventId): Result<Timeline>
|
||||
|
||||
/**
|
||||
* Create a new timeline for the pinned events of the room.
|
||||
*/
|
||||
suspend fun pinnedEventsTimeline(): Result<Timeline>
|
||||
|
||||
fun destroy()
|
||||
|
||||
suspend fun subscribeToSync()
|
||||
|
|
@ -180,6 +185,8 @@ interface MatrixRoom : Closeable {
|
|||
|
||||
suspend fun canUserTriggerRoomNotification(userId: UserId): Result<Boolean>
|
||||
|
||||
suspend fun canUserPinUnpin(userId: UserId): Result<Boolean>
|
||||
|
||||
suspend fun canUserJoinCall(userId: UserId): Result<Boolean> =
|
||||
canUserSendState(userId, StateEventType.CALL_MEMBER)
|
||||
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
val heroes: ImmutableList<MatrixUser>,
|
||||
val pinnedEventIds: ImmutableList<EventId>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -65,3 +65,8 @@ suspend fun MatrixRoom.canRedactOwn(): Result<Boolean> = canUserRedactOwn(sessio
|
|||
* Shortcut for calling [MatrixRoom.canRedactOther] with our own user.
|
||||
*/
|
||||
suspend fun MatrixRoom.canRedactOther(): Result<Boolean> = canUserRedactOther(sessionId)
|
||||
|
||||
/**
|
||||
* Shortcut for calling [MatrixRoom.canUserPinUnpin] with our own user.
|
||||
*/
|
||||
suspend fun MatrixRoom.canPinUnpin(): Result<Boolean> = canUserPinUnpin(sessionId)
|
||||
|
|
|
|||
|
|
@ -169,4 +169,22 @@ interface Timeline : AutoCloseable {
|
|||
): Result<MediaUploadHandler>
|
||||
|
||||
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<Boolean>
|
||||
|
||||
/**
|
||||
* 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<Boolean>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ data class EventTimelineItem(
|
|||
val eventId: EventId?,
|
||||
val transactionId: TransactionId?,
|
||||
val isEditable: Boolean,
|
||||
val canBeRepliedTo: Boolean,
|
||||
val isLocal: Boolean,
|
||||
val isOwn: Boolean,
|
||||
val isRemote: Boolean,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,14 @@ sealed interface OtherState {
|
|||
data object RoomHistoryVisibility : OtherState
|
||||
data object RoomJoinRules : OtherState
|
||||
data class RoomName(val name: String?) : OtherState
|
||||
data object RoomPinnedEvents : OtherState
|
||||
data class RoomPinnedEvents(val change: Change) : OtherState {
|
||||
enum class Change {
|
||||
ADDED,
|
||||
REMOVED,
|
||||
CHANGED
|
||||
}
|
||||
}
|
||||
|
||||
data class RoomUserPowerLevels(val users: Map<String, Long>) : OtherState
|
||||
data object RoomServerAcl : OtherState
|
||||
data class RoomThirdPartyInvite(val displayName: String?) : OtherState
|
||||
|
|
|
|||
|
|
@ -37,12 +37,13 @@ dependencies {
|
|||
debugImplementation(libs.matrix.sdk)
|
||||
}
|
||||
implementation(projects.appconfig)
|
||||
implementation(projects.libraries.di)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.di)
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
implementation(projects.libraries.network)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(projects.services.toolbox.api)
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
api(projects.libraries.matrix.api)
|
||||
implementation(libs.dagger)
|
||||
implementation(projects.libraries.core)
|
||||
|
|
|
|||
|
|
@ -23,10 +23,12 @@ import io.element.android.libraries.matrix.impl.certificates.UserCertificatesPro
|
|||
import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
|
||||
import io.element.android.libraries.matrix.impl.util.anonymizedTokens
|
||||
import io.element.android.libraries.network.useragent.UserAgentProvider
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.ClientBuilder
|
||||
import org.matrix.rustcomponents.sdk.Session
|
||||
|
|
@ -46,9 +48,18 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
private val proxyProvider: ProxyProvider,
|
||||
private val clock: SystemClock,
|
||||
private val utdTracker: UtdTracker,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) {
|
||||
suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) {
|
||||
val client = getBaseClientBuilder(sessionData.sessionPath, sessionData.passphrase)
|
||||
val client = getBaseClientBuilder(
|
||||
sessionPath = sessionData.sessionPath,
|
||||
passphrase = sessionData.passphrase,
|
||||
slidingSync = if (appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first()) {
|
||||
ClientBuilderSlidingSync.Simplified
|
||||
} else {
|
||||
ClientBuilderSlidingSync.Restored
|
||||
},
|
||||
)
|
||||
.homeserverUrl(sessionData.homeserverUrl)
|
||||
.username(sessionData.userId)
|
||||
.use { it.build() }
|
||||
|
|
@ -79,6 +90,7 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
sessionPath: String,
|
||||
passphrase: String?,
|
||||
slidingSyncProxy: String? = null,
|
||||
slidingSync: ClientBuilderSlidingSync,
|
||||
): ClientBuilder {
|
||||
return ClientBuilder()
|
||||
.sessionPath(sessionPath)
|
||||
|
|
@ -88,6 +100,13 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
.addRootCertificates(userCertificatesProvider.provides())
|
||||
.autoEnableBackups(true)
|
||||
.autoEnableCrossSigning(true)
|
||||
.run {
|
||||
when (slidingSync) {
|
||||
ClientBuilderSlidingSync.Restored -> this
|
||||
ClientBuilderSlidingSync.Discovered -> requiresSlidingSync()
|
||||
ClientBuilderSlidingSync.Simplified -> simplifiedSlidingSync(true)
|
||||
}
|
||||
}
|
||||
.run {
|
||||
// Workaround for non-nullable proxy parameter in the SDK, since each call to the ClientBuilder returns a new reference we need to keep
|
||||
proxyProvider.provides()?.let { proxy(it) } ?: this
|
||||
|
|
@ -95,6 +114,17 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
enum class ClientBuilderSlidingSync {
|
||||
// The proxy will be supplied when restoring the Session.
|
||||
Restored,
|
||||
|
||||
// A proxy must be discovered whilst building the session.
|
||||
Discovered,
|
||||
|
||||
// Use Simplified Sliding Sync (discovery isn't a thing yet).
|
||||
Simplified,
|
||||
}
|
||||
|
||||
private fun SessionData.toSession() = Session(
|
||||
accessToken = accessToken,
|
||||
refreshToken = refreshToken,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.auth.OidcDetails
|
|||
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
|
||||
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.impl.ClientBuilderSlidingSync
|
||||
import io.element.android.libraries.matrix.impl.RustMatrixClientFactory
|
||||
import io.element.android.libraries.matrix.impl.auth.qrlogin.QrErrorMapper
|
||||
import io.element.android.libraries.matrix.impl.auth.qrlogin.SdkQrCodeLoginData
|
||||
|
|
@ -59,7 +60,7 @@ import javax.inject.Inject
|
|||
@ContributesBinding(AppScope::class)
|
||||
@SingleIn(AppScope::class)
|
||||
class RustMatrixAuthenticationService @Inject constructor(
|
||||
baseDirectory: File,
|
||||
private val baseDirectory: File,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
private val sessionStore: SessionStore,
|
||||
private val rustMatrixClientFactory: RustMatrixClientFactory,
|
||||
|
|
@ -69,10 +70,19 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
// Passphrase which will be used for new sessions. Existing sessions will use the passphrase
|
||||
// stored in the SessionData.
|
||||
private val pendingPassphrase = getDatabasePassphrase()
|
||||
private val sessionPath = File(baseDirectory, UUID.randomUUID().toString()).absolutePath
|
||||
|
||||
// Need to keep a copy of the current session path to eventually delete it.
|
||||
// Ideally it would be possible to get the sessionPath from the Client to avoid doing this.
|
||||
private var sessionPath: File? = null
|
||||
private var currentClient: Client? = null
|
||||
private var currentHomeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
|
||||
|
||||
private fun rotateSessionPath(): File {
|
||||
sessionPath?.deleteRecursively()
|
||||
return File(baseDirectory, UUID.randomUUID().toString())
|
||||
.also { sessionPath = it }
|
||||
}
|
||||
|
||||
override fun loggedInStateFlow(): Flow<LoggedInState> {
|
||||
return sessionStore.isLoggedIn()
|
||||
}
|
||||
|
|
@ -116,8 +126,9 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
|
||||
override suspend fun setHomeserver(homeserver: String): Result<Unit> =
|
||||
withContext(coroutineDispatchers.io) {
|
||||
val emptySessionPath = rotateSessionPath()
|
||||
runCatching {
|
||||
val client = getBaseClientBuilder()
|
||||
val client = getBaseClientBuilder(emptySessionPath)
|
||||
.serverNameOrHomeserverUrl(homeserver)
|
||||
.build()
|
||||
currentClient = client
|
||||
|
|
@ -134,13 +145,14 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
withContext(coroutineDispatchers.io) {
|
||||
runCatching {
|
||||
val client = currentClient ?: error("You need to call `setHomeserver()` first")
|
||||
val currentSessionPath = sessionPath ?: error("You need to call `setHomeserver()` first")
|
||||
client.login(username, password, "Element X Android", null)
|
||||
val sessionData = client.session()
|
||||
.toSessionData(
|
||||
isTokenValid = true,
|
||||
loginType = LoginType.PASSWORD,
|
||||
passphrase = pendingPassphrase,
|
||||
sessionPath = sessionPath,
|
||||
sessionPath = currentSessionPath.absolutePath,
|
||||
)
|
||||
clear()
|
||||
sessionStore.storeData(sessionData)
|
||||
|
|
@ -184,13 +196,14 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
return withContext(coroutineDispatchers.io) {
|
||||
runCatching {
|
||||
val client = currentClient ?: error("You need to call `setHomeserver()` first")
|
||||
val currentSessionPath = sessionPath ?: error("You need to call `setHomeserver()` first")
|
||||
val urlForOidcLogin = pendingOidcAuthorizationData ?: error("You need to call `getOidcUrl()` first")
|
||||
client.loginWithOidcCallback(urlForOidcLogin, callbackUrl)
|
||||
val sessionData = client.session().toSessionData(
|
||||
isTokenValid = true,
|
||||
loginType = LoginType.OIDC,
|
||||
passphrase = pendingPassphrase,
|
||||
sessionPath = sessionPath,
|
||||
sessionPath = currentSessionPath.absolutePath,
|
||||
)
|
||||
clear()
|
||||
pendingOidcAuthorizationData?.close()
|
||||
|
|
@ -205,11 +218,13 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
|
||||
override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) =
|
||||
withContext(coroutineDispatchers.io) {
|
||||
val emptySessionPath = rotateSessionPath()
|
||||
runCatching {
|
||||
val client = rustMatrixClientFactory.getBaseClientBuilder(
|
||||
sessionPath = sessionPath,
|
||||
sessionPath = emptySessionPath.absolutePath,
|
||||
passphrase = pendingPassphrase,
|
||||
slidingSyncProxy = AuthenticationConfig.SLIDING_SYNC_PROXY_URL,
|
||||
slidingSync = ClientBuilderSlidingSync.Discovered,
|
||||
)
|
||||
.buildWithQrCode(
|
||||
qrCodeData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData,
|
||||
|
|
@ -227,7 +242,7 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
isTokenValid = true,
|
||||
loginType = LoginType.QR,
|
||||
passphrase = pendingPassphrase,
|
||||
sessionPath = sessionPath,
|
||||
sessionPath = emptySessionPath.absolutePath,
|
||||
)
|
||||
sessionStore.storeData(sessionData)
|
||||
SessionId(sessionData.userId)
|
||||
|
|
@ -244,15 +259,17 @@ class RustMatrixAuthenticationService @Inject constructor(
|
|||
}
|
||||
Timber.e(throwable, "Failed to login with QR code")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBaseClientBuilder() = rustMatrixClientFactory
|
||||
private fun getBaseClientBuilder(
|
||||
sessionPath: File,
|
||||
) = rustMatrixClientFactory
|
||||
.getBaseClientBuilder(
|
||||
sessionPath = sessionPath,
|
||||
sessionPath = sessionPath.absolutePath,
|
||||
passphrase = pendingPassphrase,
|
||||
slidingSyncProxy = AuthenticationConfig.SLIDING_SYNC_PROXY_URL,
|
||||
slidingSync = ClientBuilderSlidingSync.Discovered,
|
||||
)
|
||||
.requiresSlidingSync()
|
||||
|
||||
private fun clear() {
|
||||
currentClient?.close()
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,23 +51,15 @@ class RoomSyncSubscriber(
|
|||
includeHeroes = false,
|
||||
)
|
||||
|
||||
suspend fun subscribe(roomId: RoomId) = mutex.withLock {
|
||||
withContext(dispatchers.io) {
|
||||
try {
|
||||
subscribeToRoom(roomId)
|
||||
} catch (exception: Exception) {
|
||||
Timber.e("Failed to subscribe to room $roomId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun batchSubscribe(roomIds: List<RoomId>) = mutex.withLock {
|
||||
withContext(dispatchers.io) {
|
||||
for (roomId in roomIds) {
|
||||
suspend fun subscribe(roomId: RoomId) {
|
||||
mutex.withLock {
|
||||
withContext(dispatchers.io) {
|
||||
try {
|
||||
subscribeToRoom(roomId)
|
||||
} catch (cancellationException: CancellationException) {
|
||||
throw cancellationException
|
||||
if (!isSubscribedTo(roomId)) {
|
||||
Timber.d("Subscribing to room $roomId}")
|
||||
roomListService.subscribeToRooms(listOf(roomId.value), settings)
|
||||
}
|
||||
subscribedRoomIds.add(roomId)
|
||||
} catch (exception: Exception) {
|
||||
Timber.e("Failed to subscribe to room $roomId")
|
||||
}
|
||||
|
|
@ -75,14 +67,21 @@ class RoomSyncSubscriber(
|
|||
}
|
||||
}
|
||||
|
||||
private fun subscribeToRoom(roomId: RoomId) {
|
||||
if (!isSubscribedTo(roomId)) {
|
||||
Timber.d("Subscribing to room $roomId}")
|
||||
roomListService.room(roomId.value).use { roomListItem ->
|
||||
roomListItem.subscribe(settings)
|
||||
suspend fun batchSubscribe(roomIds: List<RoomId>) = mutex.withLock {
|
||||
withContext(dispatchers.io) {
|
||||
try {
|
||||
val roomIdsToSubscribeTo = roomIds.filterNot { isSubscribedTo(it) }
|
||||
if (roomIdsToSubscribeTo.isNotEmpty()) {
|
||||
Timber.d("Subscribing to rooms: $roomIds")
|
||||
roomListService.subscribeToRooms(roomIdsToSubscribeTo.map { it.value }, settings)
|
||||
subscribedRoomIds.addAll(roomIds)
|
||||
}
|
||||
} catch (cancellationException: CancellationException) {
|
||||
throw cancellationException
|
||||
} catch (exception: Exception) {
|
||||
Timber.e(exception, "Failed to subscribe to rooms: $roomIds")
|
||||
}
|
||||
}
|
||||
subscribedRoomIds.add(roomId)
|
||||
}
|
||||
|
||||
fun isSubscribedTo(roomId: RoomId): Boolean {
|
||||
|
|
|
|||
|
|
@ -192,6 +192,21 @@ class RustMatrixRoom(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun pinnedEventsTimeline(): Result<Timeline> {
|
||||
return runCatching {
|
||||
innerRoom.pinnedEventsTimeline(
|
||||
internalIdPrefix = "pinned_events",
|
||||
maxEventsToLoad = 100u,
|
||||
).let { inner ->
|
||||
createTimeline(inner, isLive = false)
|
||||
}
|
||||
}.onFailure {
|
||||
if (it is CancellationException) {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
roomCoroutineScope.cancel()
|
||||
liveTimeline.close()
|
||||
|
|
@ -403,6 +418,12 @@ class RustMatrixRoom(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserPinUnpin(userId: UserId): Result<Boolean> {
|
||||
return runCatching {
|
||||
innerRoom.canUserPinUnpin(userId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendImage(
|
||||
file: File,
|
||||
thumbnailFile: File?,
|
||||
|
|
|
|||
|
|
@ -525,6 +525,18 @@ class RustTimeline(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun pinEvent(eventId: EventId): Result<Boolean> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
inner.pinEvent(eventId = eventId.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unpinEvent(eventId: EventId): Result<Boolean> = withContext(dispatcher) {
|
||||
runCatching {
|
||||
inner.unpinEvent(eventId = eventId.value)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchDetailsForEvent(eventId: EventId): Result<Unit> {
|
||||
return runCatching {
|
||||
inner.fetchDetailsForEvent(eventId.value)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMap
|
|||
eventId = it.eventId()?.let(::EventId),
|
||||
transactionId = it.transactionId()?.let(::TransactionId),
|
||||
isEditable = it.isEditable(),
|
||||
canBeRepliedTo = it.canBeRepliedTo(),
|
||||
isLocal = it.isLocal(),
|
||||
isOwn = it.isOwn(),
|
||||
isRemote = it.isRemote(),
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import kotlinx.collections.immutable.toImmutableMap
|
|||
import org.matrix.rustcomponents.sdk.TimelineItemContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import uniffi.matrix_sdk_ui.RoomPinnedEventsChange
|
||||
import org.matrix.rustcomponents.sdk.EncryptedMessage as RustEncryptedMessage
|
||||
import org.matrix.rustcomponents.sdk.MembershipChange as RustMembershipChange
|
||||
import org.matrix.rustcomponents.sdk.OtherState as RustOtherState
|
||||
|
|
@ -176,7 +177,7 @@ private fun RustOtherState.map(): OtherState {
|
|||
RustOtherState.RoomHistoryVisibility -> OtherState.RoomHistoryVisibility
|
||||
RustOtherState.RoomJoinRules -> OtherState.RoomJoinRules
|
||||
is RustOtherState.RoomName -> OtherState.RoomName(name)
|
||||
RustOtherState.RoomPinnedEvents -> OtherState.RoomPinnedEvents
|
||||
is RustOtherState.RoomPinnedEvents -> OtherState.RoomPinnedEvents(change.map())
|
||||
is RustOtherState.RoomPowerLevels -> OtherState.RoomUserPowerLevels(users)
|
||||
RustOtherState.RoomServerAcl -> OtherState.RoomServerAcl
|
||||
is RustOtherState.RoomThirdPartyInvite -> OtherState.RoomThirdPartyInvite(displayName)
|
||||
|
|
@ -187,6 +188,14 @@ private fun RustOtherState.map(): OtherState {
|
|||
}
|
||||
}
|
||||
|
||||
private fun RoomPinnedEventsChange.map(): OtherState.RoomPinnedEvents.Change {
|
||||
return when (this) {
|
||||
RoomPinnedEventsChange.ADDED -> OtherState.RoomPinnedEvents.Change.ADDED
|
||||
RoomPinnedEventsChange.REMOVED -> OtherState.RoomPinnedEvents.Change.REMOVED
|
||||
RoomPinnedEventsChange.CHANGED -> OtherState.RoomPinnedEvents.Change.CHANGED
|
||||
}
|
||||
}
|
||||
|
||||
private fun RustEncryptedMessage.map(): UnableToDecryptContent.Data {
|
||||
return when (this) {
|
||||
is RustEncryptedMessage.MegolmV1AesSha2 -> UnableToDecryptContent.Data.MegolmV1AesSha2(sessionId, cause.map())
|
||||
|
|
|
|||
|
|
@ -189,6 +189,8 @@ class RoomSummaryListProcessorTest {
|
|||
override fun syncIndicator(delayBeforeShowingInMs: UInt, delayBeforeHidingInMs: UInt, listener: RoomListServiceSyncIndicatorListener): TaskHandle {
|
||||
return TaskHandle(Pointer.NULL)
|
||||
}
|
||||
|
||||
override fun subscribeToRooms(roomIds: List<String>, settings: RoomSubscription?) = Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,6 +223,7 @@ private fun aRustRoomInfo(
|
|||
numUnreadMessages: ULong = 0uL,
|
||||
numUnreadNotifications: ULong = 0uL,
|
||||
numUnreadMentions: ULong = 0uL,
|
||||
pinnedEventIds: List<String> = listOf(),
|
||||
) = RoomInfo(
|
||||
id = id,
|
||||
displayName = displayName,
|
||||
|
|
@ -249,7 +252,8 @@ private fun aRustRoomInfo(
|
|||
isMarkedUnread = isMarkedUnread,
|
||||
numUnreadMessages = numUnreadMessages,
|
||||
numUnreadNotifications = numUnreadNotifications,
|
||||
numUnreadMentions = numUnreadMentions
|
||||
numUnreadMentions = numUnreadMentions,
|
||||
pinnedEventIds = pinnedEventIds,
|
||||
)
|
||||
|
||||
class FakeRoomListItem(
|
||||
|
|
@ -268,6 +272,4 @@ class FakeRoomListItem(
|
|||
override suspend fun latestEvent(): EventTimelineItem? {
|
||||
return latestEvent
|
||||
}
|
||||
|
||||
override fun subscribe(settings: RoomSubscription?) = Unit
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ class FakeMatrixRoom(
|
|||
private val getWidgetDriverResult: (MatrixWidgetSettings) -> Result<MatrixWidgetDriver> = { lambdaError() },
|
||||
private val canUserTriggerRoomNotificationResult: (UserId) -> Result<Boolean> = { lambdaError() },
|
||||
private val canUserJoinCallResult: (UserId) -> Result<Boolean> = { lambdaError() },
|
||||
private val canUserPinUnpinResult: (UserId) -> Result<Boolean> = { lambdaError() },
|
||||
private val setIsFavoriteResult: (Boolean) -> Result<Unit> = { lambdaError() },
|
||||
private val powerLevelsResult: () -> Result<MatrixRoomPowerLevels> = { lambdaError() },
|
||||
private val updatePowerLevelsResult: () -> Result<Unit> = { lambdaError() },
|
||||
|
|
@ -134,10 +135,12 @@ class FakeMatrixRoom(
|
|||
private val updateMembersResult: () -> Unit = { lambdaError() },
|
||||
private val getMembersResult: (Int) -> Result<List<RoomMember>> = { lambdaError() },
|
||||
private val timelineFocusedOnEventResult: (EventId) -> Result<Timeline> = { lambdaError() },
|
||||
private val pinnedEventsTimelineResult: () -> Result<Timeline> = { lambdaError() },
|
||||
private val setSendQueueEnabledLambda: (Boolean) -> Unit = { _: Boolean -> },
|
||||
private val saveComposerDraftLambda: (ComposerDraft) -> Result<Unit> = { _: ComposerDraft -> Result.success(Unit) },
|
||||
private val loadComposerDraftLambda: () -> Result<ComposerDraft?> = { Result.success<ComposerDraft?>(null) },
|
||||
private val clearComposerDraftLambda: () -> Result<Unit> = { Result.success(Unit) },
|
||||
private val subscribeToSyncLambda: () -> Unit = { lambdaError() },
|
||||
) : MatrixRoom {
|
||||
private val _roomInfoFlow: MutableSharedFlow<MatrixRoomInfo> = MutableSharedFlow(replay = 1)
|
||||
override val roomInfoFlow: Flow<MatrixRoomInfo> = _roomInfoFlow
|
||||
|
|
@ -180,7 +183,13 @@ class FakeMatrixRoom(
|
|||
timelineFocusedOnEventResult(eventId)
|
||||
}
|
||||
|
||||
override suspend fun subscribeToSync() = Unit
|
||||
override suspend fun pinnedEventsTimeline(): Result<Timeline> = simulateLongTask {
|
||||
pinnedEventsTimelineResult()
|
||||
}
|
||||
|
||||
override suspend fun subscribeToSync() {
|
||||
subscribeToSyncLambda()
|
||||
}
|
||||
|
||||
override suspend fun powerLevels(): Result<MatrixRoomPowerLevels> {
|
||||
return powerLevelsResult()
|
||||
|
|
@ -289,6 +298,10 @@ class FakeMatrixRoom(
|
|||
return canUserJoinCallResult(userId)
|
||||
}
|
||||
|
||||
override suspend fun canUserPinUnpin(userId: UserId): Result<Boolean> {
|
||||
return canUserPinUnpinResult(userId)
|
||||
}
|
||||
|
||||
override suspend fun sendImage(
|
||||
file: File,
|
||||
thumbnailFile: File?,
|
||||
|
|
@ -517,6 +530,7 @@ fun aRoomInfo(
|
|||
userPowerLevels: ImmutableMap<UserId, Long> = persistentMapOf(),
|
||||
activeRoomCallParticipants: List<String> = emptyList(),
|
||||
heroes: List<MatrixUser> = emptyList(),
|
||||
pinnedEventIds: List<EventId> = emptyList(),
|
||||
) = MatrixRoomInfo(
|
||||
id = id,
|
||||
name = name,
|
||||
|
|
@ -542,6 +556,7 @@ fun aRoomInfo(
|
|||
userPowerLevels = userPowerLevels,
|
||||
activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(),
|
||||
heroes = heroes.toImmutableList(),
|
||||
pinnedEventIds = pinnedEventIds.toImmutableList(),
|
||||
)
|
||||
|
||||
fun defaultRoomPowerLevels() = MatrixRoomPowerLevels(
|
||||
|
|
|
|||
|
|
@ -22,22 +22,16 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class FakeSyncService(
|
||||
initialState: SyncState = SyncState.Idle
|
||||
syncStateFlow: MutableStateFlow<SyncState> = MutableStateFlow(SyncState.Idle)
|
||||
) : SyncService {
|
||||
private val syncStateFlow = MutableStateFlow(initialState)
|
||||
|
||||
fun simulateError() {
|
||||
syncStateFlow.value = SyncState.Error
|
||||
}
|
||||
|
||||
var startSyncLambda: () -> Result<Unit> = { Result.success(Unit) }
|
||||
override suspend fun startSync(): Result<Unit> {
|
||||
syncStateFlow.value = SyncState.Running
|
||||
return Result.success(Unit)
|
||||
return startSyncLambda()
|
||||
}
|
||||
|
||||
var stopSyncLambda: () -> Result<Unit> = { Result.success(Unit) }
|
||||
override suspend fun stopSync(): Result<Unit> {
|
||||
syncStateFlow.value = SyncState.Terminated
|
||||
return Result.success(Unit)
|
||||
return stopSyncLambda()
|
||||
}
|
||||
|
||||
override val syncState: StateFlow<SyncState> = syncStateFlow
|
||||
|
|
|
|||
|
|
@ -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<Boolean> = { lambdaError() }
|
||||
override suspend fun pinEvent(eventId: EventId): Result<Boolean> {
|
||||
return pinEventLambda(eventId)
|
||||
}
|
||||
|
||||
var unpinEventLambda: (eventId: EventId) -> Result<Boolean> = { lambdaError() }
|
||||
override suspend fun unpinEvent(eventId: EventId): Result<Boolean> {
|
||||
return unpinEventLambda(eventId)
|
||||
}
|
||||
|
||||
var closeCounter = 0
|
||||
private set
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ fun anEventTimelineItem(
|
|||
eventId: EventId = AN_EVENT_ID,
|
||||
transactionId: TransactionId? = null,
|
||||
isEditable: Boolean = false,
|
||||
canBeRepliedTo: Boolean = false,
|
||||
isLocal: Boolean = false,
|
||||
isOwn: Boolean = false,
|
||||
isRemote: Boolean = false,
|
||||
|
|
@ -63,6 +64,7 @@ fun anEventTimelineItem(
|
|||
eventId = eventId,
|
||||
transactionId = transactionId,
|
||||
isEditable = isEditable,
|
||||
canBeRepliedTo = canBeRepliedTo,
|
||||
isLocal = isLocal,
|
||||
isOwn = isOwn,
|
||||
isRemote = isRemote,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue