Merge branch 'develop' into julioromano/poll_history_entry_point
This commit is contained in:
commit
72cdc0eee0
738 changed files with 9387 additions and 1581 deletions
|
|
@ -41,7 +41,7 @@ interface MatrixClient : Closeable {
|
|||
val roomListService: RoomListService
|
||||
val mediaLoader: MatrixMediaLoader
|
||||
suspend fun getRoom(roomId: RoomId): MatrixRoom?
|
||||
suspend fun findDM(userId: UserId): MatrixRoom?
|
||||
suspend fun findDM(userId: UserId): RoomId?
|
||||
suspend fun ignoreUser(userId: UserId): Result<Unit>
|
||||
suspend fun unignoreUser(userId: UserId): Result<Unit>
|
||||
suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId>
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ interface EncryptionService {
|
|||
suspend fun doesBackupExistOnServer(): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Note: accept bot recoveryKey and passphrase.
|
||||
* Note: accept both recoveryKey and passphrase.
|
||||
*/
|
||||
suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit>
|
||||
suspend fun recover(recoveryKey: String): Result<Unit>
|
||||
|
||||
/**
|
||||
* Wait for backup upload steady state.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.encryption
|
||||
|
||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
|
||||
sealed class RecoveryException(message: String) : Exception(message) {
|
||||
class SecretStorage(message: String) : RecoveryException(message)
|
||||
data object BackupExistsOnServer : RecoveryException("BackupExistsOnServer")
|
||||
data class Client(val exception: ClientException) : RecoveryException(exception.message ?: "Unknown error")
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.mxc
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
class MxcTools @Inject constructor() {
|
||||
/**
|
||||
* Regex to match a Matrix Content (mxc://) URI.
|
||||
*
|
||||
* See: https://spec.matrix.org/v1.8/client-server-api/#matrix-content-mxc-uris
|
||||
*/
|
||||
private val mxcRegex = Regex("""^mxc://([^/]+)/([^/]+)$""")
|
||||
|
||||
/**
|
||||
* Sanitizes an mxcUri to be used as a relative file path.
|
||||
*
|
||||
* @param mxcUri the Matrix Content (mxc://) URI of the file.
|
||||
* @return the relative file path as "<server-name>/<media-id>" or null if the mxcUri is invalid.
|
||||
*/
|
||||
fun mxcUri2FilePath(mxcUri: String): String? = mxcRegex.matchEntire(mxcUri)?.let { match ->
|
||||
buildString {
|
||||
append(match.groupValues[1])
|
||||
append("/")
|
||||
append(match.groupValues[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageType
|
|||
data class NotificationData(
|
||||
val eventId: EventId,
|
||||
val roomId: RoomId,
|
||||
// mxc url
|
||||
val senderAvatarUrl: String?,
|
||||
val senderDisplayName: String?,
|
||||
val roomAvatarUrl: String?,
|
||||
|
|
@ -34,8 +35,6 @@ data class NotificationData(
|
|||
val isNoisy: Boolean,
|
||||
val timestamp: Long,
|
||||
val content: NotificationContent,
|
||||
// For images for instance
|
||||
val contentUrl: String?,
|
||||
val hasMention: Boolean,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -38,5 +38,7 @@ interface NotificationSettingsService {
|
|||
suspend fun setRoomMentionEnabled(enabled: Boolean): Result<Unit>
|
||||
suspend fun isCallEnabled(): Result<Boolean>
|
||||
suspend fun setCallEnabled(enabled: Boolean): Result<Unit>
|
||||
suspend fun isInviteForMeEnabled(): Result<Boolean>
|
||||
suspend fun setInviteForMeEnabled(enabled: Boolean): Result<Unit>
|
||||
suspend fun getRoomsWithUserDefinedRules(): Result<List<String>>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,8 @@ interface MatrixRoom : Closeable {
|
|||
|
||||
suspend fun canUserTriggerRoomNotification(userId: UserId): Result<Boolean>
|
||||
|
||||
suspend fun canUserJoinCall(userId: UserId): Result<Boolean>
|
||||
suspend fun canUserJoinCall(userId: UserId): Result<Boolean> =
|
||||
canUserSendState(userId, StateEventType.CALL_MEMBER)
|
||||
|
||||
suspend fun updateAvatar(mimeType: String, data: ByteArray): Result<Unit>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
/**
|
||||
* Try to find an existing DM with the given user, or create one if none exists.
|
||||
*/
|
||||
suspend fun MatrixClient.startDM(userId: UserId): StartDMResult {
|
||||
val existingDM = findDM(userId)
|
||||
return if (existingDM != null) {
|
||||
StartDMResult.Success(existingDM, isNew = false)
|
||||
} else {
|
||||
createDM(userId).fold(
|
||||
{ StartDMResult.Success(it, isNew = true) },
|
||||
{ StartDMResult.Failure(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface StartDMResult {
|
||||
data class Success(val roomId: RoomId, val isNew: Boolean) : StartDMResult
|
||||
data class Failure(val throwable: Throwable) : StartDMResult
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ enum class StateEventType {
|
|||
POLICY_RULE_ROOM,
|
||||
POLICY_RULE_SERVER,
|
||||
POLICY_RULE_USER,
|
||||
CALL_MEMBER,
|
||||
ROOM_ALIASES,
|
||||
ROOM_AVATAR,
|
||||
ROOM_CANONICAL_ALIAS,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,24 @@
|
|||
|
||||
package io.element.android.libraries.matrix.api.verification
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
sealed interface SessionVerificationData {
|
||||
data class Emojis(
|
||||
// 7 emojis
|
||||
val emojis: List<VerificationEmoji>,
|
||||
) : SessionVerificationData
|
||||
|
||||
data class Decimals(
|
||||
// 3 numbers
|
||||
val decimals: List<Int>,
|
||||
) : SessionVerificationData
|
||||
}
|
||||
|
||||
// https://spec.matrix.org/unstable/client-server-api/#sas-method-emoji
|
||||
data class VerificationEmoji(
|
||||
val code: String,
|
||||
val name: String,
|
||||
val number: Int,
|
||||
val emoji: String,
|
||||
val description: String,
|
||||
)
|
||||
|
|
@ -17,7 +17,6 @@
|
|||
package io.element.android.libraries.matrix.api.verification
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
|
|
@ -26,7 +25,7 @@ interface SessionVerificationService {
|
|||
/**
|
||||
* State of the current verification flow ([VerificationFlowState.Initial] if not started).
|
||||
*/
|
||||
val verificationFlowState : StateFlow<VerificationFlowState>
|
||||
val verificationFlowState: StateFlow<VerificationFlowState>
|
||||
|
||||
/**
|
||||
* The internal service that checks verification can only run after the initial sync.
|
||||
|
|
@ -101,8 +100,8 @@ sealed interface VerificationFlowState {
|
|||
/** Short Authentication String (SAS) verification started between the 2 devices. */
|
||||
data object StartedSasVerification : VerificationFlowState
|
||||
|
||||
/** Verification data for the SAS verification (emojis) received. */
|
||||
data class ReceivedVerificationData(val emoji: ImmutableList<VerificationEmoji>) : VerificationFlowState
|
||||
/** Verification data for the SAS verification received. */
|
||||
data class ReceivedVerificationData(val data: SessionVerificationData) : VerificationFlowState
|
||||
|
||||
/** Verification completed successfully. */
|
||||
data object Finished : VerificationFlowState
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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.mxc
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
class MxcToolsTest {
|
||||
@Test
|
||||
fun `mxcUri2FilePath returns extracted path`() {
|
||||
val mxcTools = MxcTools()
|
||||
val mxcUri = "mxc://server.org/abc123"
|
||||
val filePath = mxcTools.mxcUri2FilePath(mxcUri)
|
||||
assertThat(filePath).isEqualTo("server.org/abc123")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `mxcUri2FilePath returns null for invalid data`() {
|
||||
val mxcTools = MxcTools()
|
||||
assertThat(mxcTools.mxcUri2FilePath("")).isNull()
|
||||
assertThat(mxcTools.mxcUri2FilePath("mxc://server.org")).isNull()
|
||||
assertThat(mxcTools.mxcUri2FilePath("mxc://server.org/")).isNull()
|
||||
assertThat(mxcTools.mxcUri2FilePath("m://server.org/abc123")).isNull()
|
||||
}
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ dependencies {
|
|||
api(projects.libraries.matrix.api)
|
||||
implementation(libs.dagger)
|
||||
implementation(projects.libraries.core)
|
||||
implementation("net.java.dev.jna:jna:5.13.0@aar")
|
||||
implementation("net.java.dev.jna:jna:5.14.0@aar")
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.serialization.json)
|
||||
implementation(libs.kotlinx.collections.immutable)
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ import org.matrix.rustcomponents.sdk.RoomVisibility as RustRoomVisibility
|
|||
import org.matrix.rustcomponents.sdk.SyncService as ClientSyncService
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class RustMatrixClient constructor(
|
||||
class RustMatrixClient(
|
||||
private val client: Client,
|
||||
private val syncService: ClientSyncService,
|
||||
private val sessionStore: SessionStore,
|
||||
|
|
@ -119,10 +119,9 @@ class RustMatrixClient constructor(
|
|||
.filterByPushRules()
|
||||
.finish()
|
||||
}
|
||||
private val notificationSettings = client.getNotificationSettings()
|
||||
|
||||
private val notificationService = RustNotificationService(sessionId, notificationClient, dispatchers, clock)
|
||||
private val notificationSettingsService = RustNotificationSettingsService(notificationSettings, dispatchers)
|
||||
private val notificationSettingsService = RustNotificationSettingsService(client, dispatchers)
|
||||
.apply { start() }
|
||||
private val roomSyncSubscriber = RoomSyncSubscriber(innerRoomListService, dispatchers)
|
||||
private val encryptionService = RustEncryptionService(
|
||||
client = client,
|
||||
|
|
@ -241,9 +240,8 @@ class RustMatrixClient constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun findDM(userId: UserId): MatrixRoom? {
|
||||
val roomId = client.getDmRoom(userId.value)?.use { RoomId(it.id()) }
|
||||
return roomId?.let { getRoom(it) }
|
||||
override suspend fun findDM(userId: UserId): RoomId? {
|
||||
return client.getDmRoom(userId.value)?.use { RoomId(it.id()) }
|
||||
}
|
||||
|
||||
override suspend fun ignoreUser(userId: UserId): Result<Unit> = withContext(sessionDispatcher) {
|
||||
|
|
@ -347,8 +345,7 @@ class RustMatrixClient constructor(
|
|||
override fun close() {
|
||||
sessionCoroutineScope.cancel()
|
||||
clientDelegateTaskHandle?.cancelAndDestroy()
|
||||
notificationSettings.setDelegate(null)
|
||||
notificationSettings.destroy()
|
||||
notificationSettingsService.destroy()
|
||||
verificationService.destroy()
|
||||
syncService.destroy()
|
||||
innerRoomListService.destroy()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.impl.encryption
|
||||
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryException
|
||||
import io.element.android.libraries.matrix.api.exception.ClientException
|
||||
import io.element.android.libraries.matrix.impl.exception.mapClientException
|
||||
import org.matrix.rustcomponents.sdk.RecoveryException as RustRecoveryException
|
||||
|
||||
fun Throwable.mapRecoveryException(): RecoveryException {
|
||||
return when (this) {
|
||||
is RustRecoveryException.SecretStorage -> RecoveryException.SecretStorage(
|
||||
message = errorMessage
|
||||
)
|
||||
is RustRecoveryException.BackupExistsOnServer -> RecoveryException.BackupExistsOnServer
|
||||
is RustRecoveryException.Client -> RecoveryException.Client(
|
||||
source.mapClientException()
|
||||
)
|
||||
else -> RecoveryException.Client(
|
||||
ClientException.Other("Unknown error")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.libraries.matrix.impl.encryption
|
||||
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.extensions.mapFailure
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||
import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress
|
||||
|
|
@ -110,6 +111,8 @@ internal class RustEncryptionService(
|
|||
override suspend fun enableBackups(): Result<Unit> = withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
service.enableBackups()
|
||||
}.mapFailure {
|
||||
it.mapRecoveryException()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,6 +130,8 @@ internal class RustEncryptionService(
|
|||
)
|
||||
// enableRecovery returns the encryption key, but we read it from the state flow
|
||||
.let { }
|
||||
}.mapFailure {
|
||||
it.mapRecoveryException()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,24 +169,32 @@ internal class RustEncryptionService(
|
|||
override suspend fun disableRecovery(): Result<Unit> = withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
service.disableRecovery()
|
||||
}.mapFailure {
|
||||
it.mapRecoveryException()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun isLastDevice(): Result<Boolean> = withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
service.isLastDevice()
|
||||
}.mapFailure {
|
||||
it.mapRecoveryException()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun resetRecoveryKey(): Result<String> = withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
service.resetRecoveryKey()
|
||||
}.mapFailure {
|
||||
it.mapRecoveryException()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit> = withContext(dispatchers.io) {
|
||||
override suspend fun recover(recoveryKey: String): Result<Unit> = withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
service.recover(recoveryKey)
|
||||
}.mapFailure {
|
||||
it.mapRecoveryException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ class SteadyStateExceptionMapper {
|
|||
fun map(data: RustSteadyStateException): SteadyStateException {
|
||||
return when (data) {
|
||||
is RustSteadyStateException.BackupDisabled -> SteadyStateException.BackupDisabled(
|
||||
message = data.message
|
||||
message = data.message.orEmpty()
|
||||
)
|
||||
is RustSteadyStateException.Connection -> SteadyStateException.Connection(
|
||||
message = data.message
|
||||
message = data.message.orEmpty()
|
||||
)
|
||||
is RustSteadyStateException.Lagged -> SteadyStateException.Lagged(
|
||||
message = data.message
|
||||
message = data.message.orEmpty()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ class NotificationMapper(
|
|||
isNoisy = item.isNoisy.orFalse(),
|
||||
timestamp = item.timestamp() ?: clock.epochMillis(),
|
||||
content = item.event.use { notificationContentMapper.map(it) },
|
||||
contentUrl = null,
|
||||
hasMention = item.hasMention.orFalse(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,16 +26,16 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
|||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.NotificationSettings
|
||||
import org.matrix.rustcomponents.sdk.Client
|
||||
import org.matrix.rustcomponents.sdk.NotificationSettingsDelegate
|
||||
import org.matrix.rustcomponents.sdk.NotificationSettingsException
|
||||
import timber.log.Timber
|
||||
|
||||
class RustNotificationSettingsService(
|
||||
private val notificationSettings: NotificationSettings,
|
||||
client: Client,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) : NotificationSettingsService {
|
||||
|
||||
private val notificationSettings = client.getNotificationSettings()
|
||||
private val _notificationSettingsChangeFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
override val notificationSettingsChangeFlow: SharedFlow<Unit> = _notificationSettingsChangeFlow.asSharedFlow()
|
||||
|
||||
|
|
@ -45,10 +45,15 @@ class RustNotificationSettingsService(
|
|||
}
|
||||
}
|
||||
|
||||
init {
|
||||
fun start() {
|
||||
notificationSettings.setDelegate(notificationSettingsDelegate)
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
notificationSettings.setDelegate(null)
|
||||
notificationSettings.destroy()
|
||||
}
|
||||
|
||||
override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result<RoomNotificationSettings> =
|
||||
runCatching {
|
||||
notificationSettings.getRoomNotificationSettings(roomId.value, isEncrypted, isOneToOne).let(RoomNotificationSettingsMapper::map)
|
||||
|
|
@ -119,6 +124,18 @@ class RustNotificationSettingsService(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun isInviteForMeEnabled(): Result<Boolean> = withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
notificationSettings.isInviteForMeEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setInviteForMeEnabled(enabled: Boolean): Result<Unit> = withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
notificationSettings.setInviteForMeEnabled(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getRoomsWithUserDefinedRules(): Result<List<String>> =
|
||||
runCatching {
|
||||
notificationSettings.getRoomsWithUserDefinedRules(enabled = true)
|
||||
|
|
|
|||
|
|
@ -360,12 +360,6 @@ class RustMatrixRoom(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserJoinCall(userId: UserId): Result<Boolean> {
|
||||
return runCatching {
|
||||
innerRoom.canUserSendState(userId.value, StateEventType.ROOM_MEMBER_EVENT.map())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> {
|
||||
return sendAttachment(listOf(file, thumbnailFile)) {
|
||||
innerTimeline.sendImage(file.path, thumbnailFile.path, imageInfo.map(), progressCallback?.toProgressWatcher())
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ fun StateEventType.map(): RustStateEventType = when (this) {
|
|||
StateEventType.POLICY_RULE_ROOM -> RustStateEventType.POLICY_RULE_ROOM
|
||||
StateEventType.POLICY_RULE_SERVER -> RustStateEventType.POLICY_RULE_SERVER
|
||||
StateEventType.POLICY_RULE_USER -> RustStateEventType.POLICY_RULE_USER
|
||||
StateEventType.CALL_MEMBER -> RustStateEventType.CALL_MEMBER
|
||||
StateEventType.ROOM_ALIASES -> RustStateEventType.ROOM_ALIASES
|
||||
StateEventType.ROOM_AVATAR -> RustStateEventType.ROOM_AVATAR
|
||||
StateEventType.ROOM_CANONICAL_ALIAS -> RustStateEventType.ROOM_CANONICAL_ALIAS
|
||||
|
|
@ -47,6 +48,7 @@ fun RustStateEventType.map(): StateEventType = when (this) {
|
|||
RustStateEventType.POLICY_RULE_ROOM -> StateEventType.POLICY_RULE_ROOM
|
||||
RustStateEventType.POLICY_RULE_SERVER -> StateEventType.POLICY_RULE_SERVER
|
||||
RustStateEventType.POLICY_RULE_USER -> StateEventType.POLICY_RULE_USER
|
||||
RustStateEventType.CALL_MEMBER -> StateEventType.CALL_MEMBER
|
||||
RustStateEventType.ROOM_ALIASES -> StateEventType.ROOM_ALIASES
|
||||
RustStateEventType.ROOM_AVATAR -> StateEventType.ROOM_AVATAR
|
||||
RustStateEventType.ROOM_CANONICAL_ALIAS -> StateEventType.ROOM_CANONICAL_ALIAS
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ package io.element.android.libraries.matrix.impl.verification
|
|||
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationEmoji
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
||||
import io.element.android.libraries.matrix.impl.sync.RustSyncService
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -33,7 +33,8 @@ import kotlinx.coroutines.launch
|
|||
import org.matrix.rustcomponents.sdk.SessionVerificationController
|
||||
import org.matrix.rustcomponents.sdk.SessionVerificationControllerDelegate
|
||||
import org.matrix.rustcomponents.sdk.SessionVerificationControllerInterface
|
||||
import org.matrix.rustcomponents.sdk.SessionVerificationEmoji
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import org.matrix.rustcomponents.sdk.SessionVerificationData as RustSessionVerificationData
|
||||
|
||||
class RustSessionVerificationService(
|
||||
private val syncService: RustSyncService,
|
||||
|
|
@ -105,12 +106,8 @@ class RustSessionVerificationService(
|
|||
updateVerificationStatus(isVerified = true)
|
||||
}
|
||||
|
||||
override fun didReceiveVerificationData(data: List<SessionVerificationEmoji>) {
|
||||
val emojis = data.map { emoji ->
|
||||
emoji.use { VerificationEmoji(it.symbol(), it.description()) }
|
||||
}
|
||||
.toImmutableList()
|
||||
_verificationFlowState.value = VerificationFlowState.ReceivedVerificationData(emojis)
|
||||
override fun didReceiveVerificationData(data: RustSessionVerificationData) {
|
||||
_verificationFlowState.value = VerificationFlowState.ReceivedVerificationData(data.map())
|
||||
}
|
||||
|
||||
// When the actual SAS verification starts
|
||||
|
|
@ -142,3 +139,28 @@ class RustSessionVerificationService(
|
|||
_sessionVerifiedStatus.value = newValue
|
||||
}
|
||||
}
|
||||
|
||||
private fun RustSessionVerificationData.map(): SessionVerificationData {
|
||||
return use { sessionVerificationData ->
|
||||
when (sessionVerificationData) {
|
||||
is RustSessionVerificationData.Emojis -> {
|
||||
SessionVerificationData.Emojis(
|
||||
emojis = sessionVerificationData.emojis.mapIndexed { index, emoji ->
|
||||
emoji.use { sessionVerificationEmoji ->
|
||||
VerificationEmoji(
|
||||
number = sessionVerificationData.indices[index].toInt(),
|
||||
emoji = sessionVerificationEmoji.symbol(),
|
||||
description = sessionVerificationEmoji.description(),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
is RustSessionVerificationData.Decimals -> {
|
||||
SessionVerificationData.Decimals(
|
||||
decimals = sessionVerificationData.values.map { it.toInt() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ import io.element.android.libraries.matrix.test.media.FakeMediaLoader
|
|||
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
|
||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||
import io.element.android.libraries.matrix.test.pushers.FakePushersService
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
|
|
@ -72,8 +71,7 @@ class FakeMatrixClient(
|
|||
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
|
||||
private var findDmResult: MatrixRoom? = FakeMatrixRoom()
|
||||
private var findDmResult: RoomId? = A_ROOM_ID
|
||||
private var logoutFailure: Throwable? = null
|
||||
private val getRoomResults = mutableMapOf<RoomId, MatrixRoom>()
|
||||
private val searchUserResults = mutableMapOf<String, Result<MatrixSearchUserResults>>()
|
||||
|
|
@ -87,7 +85,7 @@ class FakeMatrixClient(
|
|||
return getRoomResults[roomId]
|
||||
}
|
||||
|
||||
override suspend fun findDM(userId: UserId): MatrixRoom? {
|
||||
override suspend fun findDM(userId: UserId): RoomId? {
|
||||
return findDmResult
|
||||
}
|
||||
|
||||
|
|
@ -99,14 +97,11 @@ class FakeMatrixClient(
|
|||
return unignoreUserResult
|
||||
}
|
||||
|
||||
override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> {
|
||||
delay(100)
|
||||
override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> = simulateLongTask {
|
||||
return createRoomResult
|
||||
}
|
||||
|
||||
override suspend fun createDM(userId: UserId): Result<RoomId> {
|
||||
delay(100)
|
||||
createDmFailure?.let { throw it }
|
||||
override suspend fun createDM(userId: UserId): Result<RoomId> = simulateLongTask {
|
||||
return createDmResult
|
||||
}
|
||||
|
||||
|
|
@ -206,11 +201,7 @@ class FakeMatrixClient(
|
|||
unignoreUserResult = result
|
||||
}
|
||||
|
||||
fun givenCreateDmError(failure: Throwable?) {
|
||||
createDmFailure = failure
|
||||
}
|
||||
|
||||
fun givenFindDmResult(result: MatrixRoom?) {
|
||||
fun givenFindDmResult(result: RoomId?) {
|
||||
findDmResult = result
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class FakeEncryptionService : EncryptionService {
|
|||
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Starting)
|
||||
private var waitForBackupUploadSteadyStateFlow: Flow<BackupUploadState> = flowOf()
|
||||
|
||||
private var fixRecoveryIssuesFailure: Exception? = null
|
||||
private var recoverFailure: Exception? = null
|
||||
private var doesBackupExistOnServerResult: Result<Boolean> = Result.success(true)
|
||||
|
||||
override suspend fun enableBackups(): Result<Unit> = simulateLongTask {
|
||||
|
|
@ -44,8 +44,8 @@ class FakeEncryptionService : EncryptionService {
|
|||
disableRecoveryFailure = exception
|
||||
}
|
||||
|
||||
fun givenFixRecoveryIssuesFailure(exception: Exception?) {
|
||||
fixRecoveryIssuesFailure = exception
|
||||
fun givenRecoverFailure(exception: Exception?) {
|
||||
recoverFailure = exception
|
||||
}
|
||||
|
||||
override suspend fun disableRecovery(): Result<Unit> = simulateLongTask {
|
||||
|
|
@ -61,8 +61,8 @@ class FakeEncryptionService : EncryptionService {
|
|||
return doesBackupExistOnServerResult
|
||||
}
|
||||
|
||||
override suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit> = simulateLongTask {
|
||||
fixRecoveryIssuesFailure?.let { return Result.failure(it) }
|
||||
override suspend fun recover(recoveryKey: String): Result<Unit> = simulateLongTask {
|
||||
recoverFailure?.let { return Result.failure(it) }
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ class FakeNotificationSettingsService(
|
|||
private var roomNotificationMode: RoomNotificationMode = initialRoomMode
|
||||
private var roomNotificationModeIsDefault: Boolean = initialRoomModeIsDefault
|
||||
private var callNotificationsEnabled = false
|
||||
private var inviteNotificationsEnabled = false
|
||||
private var atRoomNotificationsEnabled = false
|
||||
private var setNotificationModeError: Throwable? = null
|
||||
private var restoreDefaultNotificationModeError: Throwable? = null
|
||||
|
|
@ -52,7 +53,7 @@ class FakeNotificationSettingsService(
|
|||
override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result<RoomNotificationSettings> {
|
||||
return Result.success(
|
||||
RoomNotificationSettings(
|
||||
mode = if(roomNotificationModeIsDefault) defaultEncryptedGroupRoomNotificationMode else roomNotificationMode,
|
||||
mode = if (roomNotificationModeIsDefault) defaultEncryptedGroupRoomNotificationMode else roomNotificationMode,
|
||||
isDefault = roomNotificationModeIsDefault
|
||||
)
|
||||
)
|
||||
|
|
@ -149,6 +150,15 @@ class FakeNotificationSettingsService(
|
|||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun isInviteForMeEnabled(): Result<Boolean> {
|
||||
return Result.success(inviteNotificationsEnabled)
|
||||
}
|
||||
|
||||
override suspend fun setInviteForMeEnabled(enabled: Boolean): Result<Unit> {
|
||||
inviteNotificationsEnabled = enabled
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun getRoomsWithUserDefinedRules(): Result<List<String>> {
|
||||
return Result.success(if (roomNotificationModeIsDefault) listOf() else listOf(A_ROOM_ID.value))
|
||||
}
|
||||
|
|
@ -168,5 +178,4 @@ class FakeNotificationSettingsService(
|
|||
fun givenSetDefaultNotificationModeError(throwable: Throwable?) {
|
||||
setDefaultNotificationModeError = throwable
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,10 @@
|
|||
|
||||
package io.element.android.libraries.matrix.test.verification
|
||||
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationEmoji
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -31,10 +29,9 @@ class FakeSessionVerificationService : SessionVerificationService {
|
|||
private val _sessionVerifiedStatus = MutableStateFlow<SessionVerifiedStatus>(SessionVerifiedStatus.Unknown)
|
||||
private var _verificationFlowState = MutableStateFlow<VerificationFlowState>(VerificationFlowState.Initial)
|
||||
private var _canVerifySessionFlow = MutableStateFlow(true)
|
||||
private var emojiList = persistentListOf<VerificationEmoji>()
|
||||
var shouldFail = false
|
||||
|
||||
override val verificationFlowState: StateFlow<VerificationFlowState> =_verificationFlowState
|
||||
override val verificationFlowState: StateFlow<VerificationFlowState> = _verificationFlowState
|
||||
override val sessionVerifiedStatus: StateFlow<SessionVerifiedStatus> = _sessionVerifiedStatus
|
||||
override val canVerifySessionFlow: Flow<Boolean> = _canVerifySessionFlow
|
||||
|
||||
|
|
@ -64,8 +61,8 @@ class FakeSessionVerificationService : SessionVerificationService {
|
|||
}
|
||||
}
|
||||
|
||||
fun triggerReceiveVerificationData() {
|
||||
_verificationFlowState.value = VerificationFlowState.ReceivedVerificationData(emojiList)
|
||||
fun triggerReceiveVerificationData(sessionVerificationData: SessionVerificationData) {
|
||||
_verificationFlowState.value = VerificationFlowState.ReceivedVerificationData(sessionVerificationData)
|
||||
}
|
||||
|
||||
override suspend fun startVerification() {
|
||||
|
|
@ -88,10 +85,6 @@ class FakeSessionVerificationService : SessionVerificationService {
|
|||
_isReady.value = value
|
||||
}
|
||||
|
||||
fun givenEmojiList(emojis: List<VerificationEmoji>) {
|
||||
this.emojiList = emojis.toPersistentList()
|
||||
}
|
||||
|
||||
override suspend fun reset() {
|
||||
_verificationFlowState.value = VerificationFlowState.Initial
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue