forked from dsutanto/bChot-android
First Commit
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import config.BuildTimeConfig
|
||||
import extension.buildConfigFieldStr
|
||||
import extension.testCommonDependencies
|
||||
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
id("kotlin-parcelize")
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.matrix.api"
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
buildConfigFieldStr(
|
||||
name = "CLIENT_URI",
|
||||
value = BuildTimeConfig.URL_WEBSITE ?: "https://element.io"
|
||||
)
|
||||
buildConfigFieldStr(
|
||||
name = "LOGO_URI",
|
||||
value = BuildTimeConfig.URL_LOGO ?: "https://element.io/mobile-icon.png"
|
||||
)
|
||||
buildConfigFieldStr(
|
||||
name = "TOS_URI",
|
||||
value = BuildTimeConfig.URL_ACCEPTABLE_USE ?: "https://element.io/acceptable-use-policy-terms"
|
||||
)
|
||||
buildConfigFieldStr(
|
||||
name = "POLICY_URI",
|
||||
value = BuildTimeConfig.URL_POLICY ?: "https://element.io/privacy"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.di)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(libs.serialization.json)
|
||||
api(projects.libraries.sessionStorage.api)
|
||||
implementation(libs.coroutines.core)
|
||||
api(projects.libraries.architecture)
|
||||
|
||||
testCommonDependencies(libs)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2025 Element Creations Ltd.
|
||||
~ Copyright 2022 New Vector Ltd.
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
</manifest>
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api
|
||||
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.MatrixPatterns
|
||||
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.RoomIdOrAlias
|
||||
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.createroom.CreateRoomParameters
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewService
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
|
||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.NotJoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceService
|
||||
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.util.Optional
|
||||
|
||||
interface MatrixClient {
|
||||
val sessionId: SessionId
|
||||
val deviceId: DeviceId
|
||||
val userProfile: StateFlow<MatrixUser>
|
||||
val roomListService: RoomListService
|
||||
val spaceService: SpaceService
|
||||
val syncService: SyncService
|
||||
val sessionVerificationService: SessionVerificationService
|
||||
val pushersService: PushersService
|
||||
val notificationService: NotificationService
|
||||
val notificationSettingsService: NotificationSettingsService
|
||||
val encryptionService: EncryptionService
|
||||
val roomDirectoryService: RoomDirectoryService
|
||||
val mediaPreviewService: MediaPreviewService
|
||||
val matrixMediaLoader: MatrixMediaLoader
|
||||
val sessionCoroutineScope: CoroutineScope
|
||||
val ignoredUsersFlow: StateFlow<ImmutableList<UserId>>
|
||||
val roomMembershipObserver: RoomMembershipObserver
|
||||
suspend fun getJoinedRoom(roomId: RoomId): JoinedRoom?
|
||||
suspend fun getRoom(roomId: RoomId): BaseRoom?
|
||||
suspend fun findDM(userId: UserId): Result<RoomId?>
|
||||
suspend fun getJoinedRoomIds(): Result<Set<RoomId>>
|
||||
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>
|
||||
suspend fun getProfile(userId: UserId): Result<MatrixUser>
|
||||
suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults>
|
||||
suspend fun setDisplayName(displayName: String): Result<Unit>
|
||||
suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result<Unit>
|
||||
suspend fun removeAvatar(): Result<Unit>
|
||||
suspend fun joinRoom(roomId: RoomId): Result<RoomInfo?>
|
||||
suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomInfo?>
|
||||
suspend fun knockRoom(roomIdOrAlias: RoomIdOrAlias, message: String, serverNames: List<String>): Result<RoomInfo?>
|
||||
suspend fun getCacheSize(): Long
|
||||
|
||||
/**
|
||||
* Will close the client and delete the cache data.
|
||||
*/
|
||||
suspend fun clearCache()
|
||||
|
||||
/**
|
||||
* Logout the user.
|
||||
*
|
||||
* @param userInitiated if false, the logout came from the HS, no request will be made and the session entry will be kept in the store.
|
||||
* @param ignoreSdkError if true, the SDK will ignore any error and delete the session data anyway.
|
||||
*/
|
||||
suspend fun logout(userInitiated: Boolean, ignoreSdkError: Boolean)
|
||||
|
||||
/**
|
||||
* Retrieve the user profile, will also eventually emit a new value to [userProfile].
|
||||
*/
|
||||
suspend fun getUserProfile(): Result<MatrixUser>
|
||||
suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?>
|
||||
suspend fun uploadMedia(mimeType: String, data: ByteArray): Result<String>
|
||||
|
||||
/**
|
||||
* Get a room info flow for a given room ID.
|
||||
* The flow will emit a new value whenever the room info is updated.
|
||||
* The flow will emit Optional.empty item if the room is not found.
|
||||
*/
|
||||
fun getRoomInfoFlow(roomId: RoomId): Flow<Optional<RoomInfo>>
|
||||
|
||||
fun isMe(userId: UserId?) = userId == sessionId
|
||||
|
||||
suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit>
|
||||
suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>>
|
||||
|
||||
/**
|
||||
* Resolves the given room alias to a roomID (and a list of servers), if possible.
|
||||
* @param roomAlias the room alias to resolve
|
||||
* @return the resolved room alias if any, an empty result if not found,or an error if the resolution failed.
|
||||
*
|
||||
*/
|
||||
suspend fun resolveRoomAlias(roomAlias: RoomAlias): Result<Optional<ResolvedRoomAlias>>
|
||||
|
||||
/**
|
||||
* Enables or disables the sending queue, according to the given parameter.
|
||||
*
|
||||
* The sending queue automatically disables itself whenever sending an
|
||||
* event with it failed (e.g. sending an event via the Timeline),
|
||||
* so it's required to manually re-enable it as soon as
|
||||
* connectivity is back on the device.
|
||||
*/
|
||||
suspend fun setAllSendQueuesEnabled(enabled: Boolean)
|
||||
|
||||
/**
|
||||
* Returns a flow of room IDs that have send queue being disabled.
|
||||
* This flow will emit a new value whenever the send queue is disabled for a room.
|
||||
*/
|
||||
fun sendQueueDisabledFlow(): Flow<RoomId>
|
||||
|
||||
/**
|
||||
* Return the server name part of the current user ID, using the SDK, and if a failure occurs,
|
||||
* compute it manually.
|
||||
*/
|
||||
fun userIdServerName(): String
|
||||
|
||||
/**
|
||||
* Execute generic GET requests through the SDKs internal HTTP client.
|
||||
*/
|
||||
suspend fun getUrl(url: String): Result<ByteArray>
|
||||
|
||||
/**
|
||||
* Get a room preview for a given room ID or alias. This is especially useful for rooms that the user is not a member of, or hasn't joined yet.
|
||||
*/
|
||||
suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<NotJoinedRoom>
|
||||
|
||||
/**
|
||||
* Returns the currently used sliding sync version.
|
||||
*/
|
||||
suspend fun currentSlidingSyncVersion(): Result<SlidingSyncVersion>
|
||||
|
||||
fun canDeactivateAccount(): Boolean
|
||||
suspend fun deactivateAccount(password: String, eraseData: Boolean): Result<Unit>
|
||||
|
||||
/**
|
||||
* Check if the user can report a room.
|
||||
*/
|
||||
suspend fun canReportRoom(): Boolean
|
||||
|
||||
/**
|
||||
* Return true if Livekit Rtc is supported, i.e. if Element Call is available.
|
||||
*/
|
||||
suspend fun isLivekitRtcSupported(): Boolean
|
||||
|
||||
/**
|
||||
* Returns the maximum file upload size allowed by the Matrix server.
|
||||
*/
|
||||
suspend fun getMaxFileUploadSize(): Result<Long>
|
||||
|
||||
/**
|
||||
* Returns the list of shared recent emoji reactions for this account.
|
||||
*/
|
||||
suspend fun getRecentEmojis(): Result<List<String>>
|
||||
|
||||
/**
|
||||
* Adds an emoji to the list of recent emoji reactions for this account.
|
||||
*/
|
||||
suspend fun addRecentEmoji(emoji: String): Result<Unit>
|
||||
|
||||
/**
|
||||
* Marks the room with the provided [roomId] as read, sending a fully read receipt for [eventId].
|
||||
*
|
||||
* This method should be used with caution as providing the [eventId] ourselves can result in incorrect read receipts.
|
||||
* Use [Timeline.markAsRead] instead when possible.
|
||||
*/
|
||||
suspend fun markRoomAsFullyRead(roomId: RoomId, eventId: EventId): Result<Unit>
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a room alias from a room alias name, or null if the name is not valid.
|
||||
* @param name the room alias name ie. the local part of the room alias.
|
||||
*/
|
||||
fun MatrixClient.roomAliasFromName(name: String): RoomAlias? {
|
||||
return name.takeIf { it.isNotEmpty() }
|
||||
?.let { "#$it:${userIdServerName()}" }
|
||||
?.takeIf { MatrixPatterns.isRoomAlias(it) }
|
||||
?.let { tryOrNull { RoomAlias(it) } }
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
interface MatrixClientProvider {
|
||||
/**
|
||||
* Can be used to get or restore a MatrixClient with the given [SessionId].
|
||||
* If a [MatrixClient] is already in memory, it'll return it. Otherwise it'll try to restore one.
|
||||
* Most of the time you want to use injected constructor instead of retrieving a MatrixClient with this provider.
|
||||
*/
|
||||
suspend fun getOrRestore(sessionId: SessionId): Result<MatrixClient>
|
||||
|
||||
/**
|
||||
* Can be used to retrieve an existing [MatrixClient] with the given [SessionId].
|
||||
* @param sessionId the [SessionId] of the [MatrixClient] to retrieve.
|
||||
* @return the [MatrixClient] if it exists.
|
||||
*/
|
||||
fun getOrNull(sessionId: SessionId): MatrixClient?
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api
|
||||
|
||||
interface SdkMetadata {
|
||||
val sdkGitSha: String
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.analytics
|
||||
|
||||
import im.vector.app.features.analytics.plan.ViewRoom
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
|
||||
fun BaseRoom.toAnalyticsViewRoom(
|
||||
trigger: ViewRoom.Trigger? = null,
|
||||
selectedSpace: BaseRoom? = null,
|
||||
viaKeyboard: Boolean? = null,
|
||||
): ViewRoom {
|
||||
val activeSpace = selectedSpace?.toActiveSpace() ?: ViewRoom.ActiveSpace.Home
|
||||
|
||||
return ViewRoom(
|
||||
isDM = info().isDirect,
|
||||
isSpace = info().isSpace,
|
||||
trigger = trigger,
|
||||
activeSpace = activeSpace,
|
||||
viaKeyboard = viaKeyboard
|
||||
)
|
||||
}
|
||||
|
||||
private fun BaseRoom.toActiveSpace(): ViewRoom.ActiveSpace {
|
||||
return if (info().isPublic == true) ViewRoom.ActiveSpace.Public else ViewRoom.ActiveSpace.Private
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth
|
||||
|
||||
enum class AuthErrorCode(val value: String) {
|
||||
UNKNOWN("M_UNKNOWN"),
|
||||
USER_DEACTIVATED("M_USER_DEACTIVATED"),
|
||||
FORBIDDEN("M_FORBIDDEN")
|
||||
}
|
||||
|
||||
// This is taken from the iOS version. It seems like currently there's no better way to extract error codes
|
||||
val AuthenticationException.errorCode: AuthErrorCode
|
||||
get() {
|
||||
val message = (this as? AuthenticationException.Generic)?.message ?: return AuthErrorCode.UNKNOWN
|
||||
return enumValues<AuthErrorCode>()
|
||||
.firstOrNull { message.contains(it.value) }
|
||||
?: AuthErrorCode.UNKNOWN
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth
|
||||
|
||||
sealed class AuthenticationException(message: String?) : Exception(message) {
|
||||
data class AccountAlreadyLoggedIn(
|
||||
val userId: String,
|
||||
) : AuthenticationException(null)
|
||||
|
||||
class InvalidServerName(message: String?) : AuthenticationException(message)
|
||||
class SlidingSyncVersion(message: String?) : AuthenticationException(message)
|
||||
class ServerUnreachable(message: String?) : AuthenticationException(message)
|
||||
class Oidc(message: String?) : AuthenticationException(message)
|
||||
class Generic(message: String?) : AuthenticationException(message)
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth
|
||||
|
||||
/**
|
||||
* Checks the homeserver's compatibility with Element X.
|
||||
*/
|
||||
interface HomeServerLoginCompatibilityChecker {
|
||||
/**
|
||||
* Performs the compatibility check given the homeserver's [url].
|
||||
* @return a `true` value if the homeserver is compatible, `false` if not, or a failure result if the check unexpectedly failed.
|
||||
*/
|
||||
suspend fun check(url: String): Result<Boolean>
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth
|
||||
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
||||
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
|
||||
|
||||
interface MatrixAuthenticationService {
|
||||
/**
|
||||
* Restore a session from a [sessionId].
|
||||
* Do not restore anything it the access token is not valid anymore.
|
||||
* Generally this method should not be used directly, prefer using [MatrixClientProvider.getOrRestore] instead.
|
||||
*/
|
||||
suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient>
|
||||
|
||||
/**
|
||||
* Set the homeserver to use for authentication, and return its details.
|
||||
*/
|
||||
suspend fun setHomeserver(homeserver: String): Result<MatrixHomeServerDetails>
|
||||
|
||||
suspend fun login(username: String, password: String): Result<SessionId>
|
||||
|
||||
/**
|
||||
* Import a session that was created using another client, for instance Element Web.
|
||||
*/
|
||||
suspend fun importCreatedSession(externalSession: ExternalSession): Result<SessionId>
|
||||
|
||||
/*
|
||||
* OIDC part.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the Oidc url to display to the user.
|
||||
*/
|
||||
suspend fun getOidcUrl(
|
||||
prompt: OidcPrompt,
|
||||
loginHint: String?,
|
||||
): Result<OidcDetails>
|
||||
|
||||
/**
|
||||
* Cancel Oidc login sequence.
|
||||
*/
|
||||
suspend fun cancelOidcLogin(): Result<Unit>
|
||||
|
||||
/**
|
||||
* Attempt to login using the [callbackUrl] provided by the Oidc page.
|
||||
*/
|
||||
suspend fun loginWithOidc(callbackUrl: String): Result<SessionId>
|
||||
|
||||
suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit): Result<SessionId>
|
||||
|
||||
/** Listen to new Matrix clients being created on authentication. */
|
||||
fun listenToNewMatrixClients(lambda: (MatrixClient) -> Unit)
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth
|
||||
|
||||
data class MatrixHomeServerDetails(
|
||||
val url: String,
|
||||
val supportsPasswordLogin: Boolean,
|
||||
val supportsOidcLogin: Boolean,
|
||||
) {
|
||||
val isSupported = supportsPasswordLogin || supportsOidcLogin
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth
|
||||
|
||||
import io.element.android.libraries.matrix.api.BuildConfig
|
||||
|
||||
object OidcConfig {
|
||||
const val CLIENT_URI = BuildConfig.CLIENT_URI
|
||||
|
||||
// Note: host must match with the host of CLIENT_URI
|
||||
const val LOGO_URI = BuildConfig.LOGO_URI
|
||||
|
||||
// Note: host must match with the host of CLIENT_URI
|
||||
const val TOS_URI = BuildConfig.TOS_URI
|
||||
|
||||
// Note: host must match with the host of CLIENT_URI
|
||||
const val POLICY_URI = BuildConfig.POLICY_URI
|
||||
|
||||
// Some homeservers/auth issuers don't support dynamic client registration, and have to be registered manually
|
||||
val STATIC_REGISTRATIONS = mapOf(
|
||||
"https://id.thirdroom.io/realms/thirdroom" to "elementx",
|
||||
)
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class OidcDetails(
|
||||
val url: String,
|
||||
) : Parcelable
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth
|
||||
|
||||
sealed interface OidcPrompt {
|
||||
/**
|
||||
* The Authorization Server should prompt the End-User for
|
||||
* reauthentication.
|
||||
*/
|
||||
data object Login : OidcPrompt
|
||||
|
||||
/**
|
||||
* The Authorization Server should prompt the End-User to create a user
|
||||
* account.
|
||||
*
|
||||
* Defined in [Initiating User Registration via OpenID Connect](https://openid.net/specs/openid-connect-prompt-create-1_0.html).
|
||||
*/
|
||||
data object Create : OidcPrompt
|
||||
|
||||
/**
|
||||
* An unknown value.
|
||||
*/
|
||||
data class Unknown(val value: String) : OidcPrompt
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth
|
||||
|
||||
interface OidcRedirectUrlProvider {
|
||||
fun provide(): String
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth.external
|
||||
|
||||
/***
|
||||
* Represents a session data of a session created by another client.
|
||||
*/
|
||||
data class ExternalSession(
|
||||
val userId: String,
|
||||
val deviceId: String,
|
||||
val accessToken: String,
|
||||
val refreshToken: String?,
|
||||
val homeserverUrl: String,
|
||||
)
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth.qrlogin
|
||||
|
||||
interface MatrixQrCodeLoginData {
|
||||
fun serverName(): String?
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth.qrlogin
|
||||
|
||||
interface MatrixQrCodeLoginDataFactory {
|
||||
fun parseQrCodeData(data: ByteArray): Result<MatrixQrCodeLoginData>
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth.qrlogin
|
||||
|
||||
sealed class QrCodeDecodeException(message: String) : Exception(message) {
|
||||
class Crypto(
|
||||
message: String,
|
||||
// val reason: Reason
|
||||
) : QrCodeDecodeException(message) {
|
||||
// We plan to restore it in the future when UniFFi can process them
|
||||
// enum class Reason {
|
||||
// NOT_ENOUGH_DATA,
|
||||
// NOT_UTF8,
|
||||
// URL_PARSE,
|
||||
// INVALID_MODE,
|
||||
// INVALID_VERSION,
|
||||
// BASE64,
|
||||
// INVALID_PREFIX
|
||||
// }
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth.qrlogin
|
||||
|
||||
sealed interface QrCodeLoginStep {
|
||||
data object Uninitialized : QrCodeLoginStep
|
||||
data class EstablishingSecureChannel(val checkCode: String) : QrCodeLoginStep
|
||||
data object Starting : QrCodeLoginStep
|
||||
data class WaitingForToken(val userCode: String) : QrCodeLoginStep
|
||||
data object SyncingSecrets : QrCodeLoginStep
|
||||
data class Failed(val error: QrLoginException) : QrCodeLoginStep
|
||||
data object Finished : QrCodeLoginStep
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth.qrlogin
|
||||
|
||||
sealed class QrLoginException : Exception() {
|
||||
data object Cancelled : QrLoginException()
|
||||
data object ConnectionInsecure : QrLoginException()
|
||||
data object Declined : QrLoginException()
|
||||
data object Expired : QrLoginException()
|
||||
data object LinkingNotSupported : QrLoginException()
|
||||
data object OidcMetadataInvalid : QrLoginException()
|
||||
data object SlidingSyncNotAvailable : QrLoginException()
|
||||
data object OtherDeviceNotSignedIn : QrLoginException()
|
||||
data object CheckCodeAlreadySent : QrLoginException()
|
||||
data object CheckCodeCannotBeSent : QrLoginException()
|
||||
data object Unknown : QrLoginException()
|
||||
data object NotFound : QrLoginException()
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
@JvmInline
|
||||
value class DeviceId(val value: String) : Serializable {
|
||||
override fun toString(): String = value
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import io.element.android.libraries.androidutils.metadata.isInDebug
|
||||
import java.io.Serializable
|
||||
|
||||
@JvmInline
|
||||
value class EventId(val value: String) : Serializable {
|
||||
init {
|
||||
if (isInDebug && !MatrixPatterns.isEventId(value)) {
|
||||
error("`$value` is not a valid event id.\nExample event id: `\$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg`.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
}
|
||||
|
||||
fun EventId.toThreadId(): ThreadId = ThreadId(value)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
@JvmInline
|
||||
value class FlowId(val value: String) : Serializable {
|
||||
override fun toString(): String = value
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
|
||||
/**
|
||||
* This class contains pattern to match the different Matrix ids
|
||||
* Ref: https://matrix.org/docs/spec/appendices#identifier-grammar
|
||||
*/
|
||||
object MatrixPatterns {
|
||||
// Note: TLD is not mandatory (localhost, IP address...)
|
||||
private const val DOMAIN_REGEX = ":[A-Za-z0-9.-]+(:[0-9]{2,5})?"
|
||||
|
||||
private const val BASE_64_ALPHABET = "[0-9A-Za-z/\\+=]+"
|
||||
private const val BASE_64_URL_SAFE_ALPHABET = "[0-9A-Za-z/\\-_]+"
|
||||
|
||||
// regex pattern to find matrix user ids in a string.
|
||||
// See https://matrix.org/docs/spec/appendices#historical-user-ids
|
||||
// Sadly, we need to relax the regex pattern a bit as there already exist some ids that don't match the spec.
|
||||
// Note: local part can be empty
|
||||
private const val MATRIX_USER_IDENTIFIER_REGEX = "^@\\S*?$DOMAIN_REGEX$"
|
||||
private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex()
|
||||
|
||||
// !localpart:domain" used in most room versions prior to MSC4291
|
||||
// Note: roomId can be arbitrary strings, including space and new line char
|
||||
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "^!.+$DOMAIN_REGEX$"
|
||||
private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = MATRIX_ROOM_IDENTIFIER_REGEX.toRegex(RegexOption.DOT_MATCHES_ALL)
|
||||
|
||||
// "!event_id_base_64" used in room versions post MSC4291
|
||||
private const val MATRIX_ROOM_IDENTIFIER_DOMAINLESS_REGEX = "!$BASE_64_URL_SAFE_ALPHABET"
|
||||
private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER_DOMAINLESS = MATRIX_ROOM_IDENTIFIER_DOMAINLESS_REGEX.toRegex()
|
||||
|
||||
// regex pattern to match room aliases.
|
||||
private const val MATRIX_ROOM_ALIAS_REGEX = "^#\\S+$DOMAIN_REGEX$"
|
||||
private val PATTERN_CONTAIN_MATRIX_ALIAS = MATRIX_ROOM_ALIAS_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// regex pattern to match event ids.
|
||||
// Sadly, we need to relax the regex pattern a bit as there already exist some ids that don't match the spec.
|
||||
// v1 and v2: arbitrary string + domain
|
||||
private const val MATRIX_EVENT_IDENTIFIER_REGEX = "^\\$.+$DOMAIN_REGEX$"
|
||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = MATRIX_EVENT_IDENTIFIER_REGEX.toRegex()
|
||||
|
||||
// v3: base64
|
||||
private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$$BASE_64_ALPHABET"
|
||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = MATRIX_EVENT_IDENTIFIER_V3_REGEX.toRegex()
|
||||
|
||||
// v4: url-safe base64
|
||||
private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$$BASE_64_URL_SAFE_ALPHABET"
|
||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = MATRIX_EVENT_IDENTIFIER_V4_REGEX.toRegex()
|
||||
|
||||
private const val MAX_IDENTIFIER_LENGTH = 255
|
||||
|
||||
/**
|
||||
* Tells if a string is a valid user Id.
|
||||
*
|
||||
* @param str the string to test
|
||||
* @return true if the string is a valid user id
|
||||
*/
|
||||
fun isUserId(str: String?): Boolean {
|
||||
return str != null &&
|
||||
str.length <= MAX_IDENTIFIER_LENGTH &&
|
||||
str matches PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if a string is a valid room id.
|
||||
*
|
||||
* @param str the string to test
|
||||
* @return true if the string is a valid room Id
|
||||
*/
|
||||
fun isRoomId(str: String?): Boolean {
|
||||
return str != null &&
|
||||
str.length <= MAX_IDENTIFIER_LENGTH &&
|
||||
(str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER_DOMAINLESS ||
|
||||
str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if a string is a valid room alias.
|
||||
*
|
||||
* @param str the string to test
|
||||
* @return true if the string is a valid room alias.
|
||||
*/
|
||||
fun isRoomAlias(str: String?): Boolean {
|
||||
return str != null &&
|
||||
str.length <= MAX_IDENTIFIER_LENGTH &&
|
||||
str matches PATTERN_CONTAIN_MATRIX_ALIAS
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if a string is a valid event id.
|
||||
*
|
||||
* @param str the string to test
|
||||
* @return true if the string is a valid event id.
|
||||
*/
|
||||
fun isEventId(str: String?): Boolean {
|
||||
return str != null &&
|
||||
str.length <= MAX_IDENTIFIER_LENGTH &&
|
||||
(str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 ||
|
||||
str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 ||
|
||||
str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if a string is a valid thread id. This is an alias for [isEventId].
|
||||
*
|
||||
* @param str the string to test
|
||||
* @return true if the string is a valid thread id.
|
||||
*/
|
||||
fun isThreadId(str: String?) = isEventId(str)
|
||||
|
||||
/**
|
||||
* Finds existing ids or aliases in a [CharSequence].
|
||||
* Note not all cases are implemented.
|
||||
*/
|
||||
fun findPatterns(text: CharSequence, permalinkParser: PermalinkParser): List<MatrixPatternResult> {
|
||||
val rawTextMatches = "\\S+$DOMAIN_REGEX".toRegex(RegexOption.IGNORE_CASE).findAll(text)
|
||||
val urlMatches = "\\[\\S+\\]\\((\\S+)\\)".toRegex(RegexOption.IGNORE_CASE).findAll(text)
|
||||
val atRoomMatches = Regex("@room").findAll(text)
|
||||
return buildList {
|
||||
for (match in rawTextMatches) {
|
||||
// Match existing id and alias patterns in the text
|
||||
val type = when {
|
||||
isUserId(match.value) -> MatrixPatternType.USER_ID
|
||||
isRoomId(match.value) -> MatrixPatternType.ROOM_ID
|
||||
isRoomAlias(match.value) -> MatrixPatternType.ROOM_ALIAS
|
||||
isEventId(match.value) -> MatrixPatternType.EVENT_ID
|
||||
else -> null
|
||||
}
|
||||
if (type != null) {
|
||||
add(MatrixPatternResult(type, match.value, match.range.first, match.range.last + 1))
|
||||
}
|
||||
}
|
||||
for (match in urlMatches) {
|
||||
// Extract the link and check if it's a valid permalink
|
||||
val urlMatch = match.groupValues[1]
|
||||
when (val permalink = permalinkParser.parse(urlMatch)) {
|
||||
is PermalinkData.UserLink -> {
|
||||
add(MatrixPatternResult(MatrixPatternType.USER_ID, permalink.userId.value, match.range.first, match.range.last + 1))
|
||||
}
|
||||
is PermalinkData.RoomLink -> {
|
||||
when (permalink.roomIdOrAlias) {
|
||||
is RoomIdOrAlias.Alias -> MatrixPatternType.ROOM_ALIAS
|
||||
is RoomIdOrAlias.Id -> if (permalink.eventId == null) MatrixPatternType.ROOM_ID else null
|
||||
}?.let { type ->
|
||||
add(MatrixPatternResult(type, permalink.roomIdOrAlias.identifier, match.range.first, match.range.last + 1))
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
for (match in atRoomMatches) {
|
||||
// Special case for `@room` mentions
|
||||
add(MatrixPatternResult(MatrixPatternType.AT_ROOM, match.value, match.range.first, match.range.last + 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class MatrixPatternType {
|
||||
USER_ID,
|
||||
ROOM_ID,
|
||||
ROOM_ALIAS,
|
||||
EVENT_ID,
|
||||
AT_ROOM
|
||||
}
|
||||
|
||||
data class MatrixPatternResult(val type: MatrixPatternType, val value: String, val start: Int, val end: Int)
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
interface ProgressCallback {
|
||||
fun onProgress(current: Long, total: Long)
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import io.element.android.libraries.androidutils.metadata.isInDebug
|
||||
import java.io.Serializable
|
||||
|
||||
@JvmInline
|
||||
value class RoomAlias(val value: String) : Serializable {
|
||||
init {
|
||||
if (isInDebug && !MatrixPatterns.isRoomAlias(value)) {
|
||||
error("`$value` is not a valid room alias.\n Example room alias: `#room_alias:domain`.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import io.element.android.libraries.androidutils.metadata.isInDebug
|
||||
import java.io.Serializable
|
||||
|
||||
@JvmInline
|
||||
value class RoomId(val value: String) : Serializable {
|
||||
init {
|
||||
if (isInDebug && !MatrixPatterns.isRoomId(value)) {
|
||||
error("`$value` is not a valid room id.\n Example room id: `!room_id:domain`.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Immutable
|
||||
sealed interface RoomIdOrAlias : Parcelable {
|
||||
@Parcelize
|
||||
@JvmInline
|
||||
value class Id(val roomId: RoomId) : RoomIdOrAlias
|
||||
|
||||
@Parcelize
|
||||
@JvmInline
|
||||
value class Alias(val roomAlias: RoomAlias) : RoomIdOrAlias
|
||||
|
||||
val identifier: String
|
||||
get() = when (this) {
|
||||
is Id -> roomId.value
|
||||
is Alias -> roomAlias.value
|
||||
}
|
||||
}
|
||||
|
||||
fun RoomId.toRoomIdOrAlias() = RoomIdOrAlias.Id(this)
|
||||
fun RoomAlias.toRoomIdOrAlias() = RoomIdOrAlias.Alias(this)
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
fun interface SendHandle {
|
||||
suspend fun retry(): Result<Unit>
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
/**
|
||||
* The [UserId] of the currently logged in user.
|
||||
*/
|
||||
typealias SessionId = UserId
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
typealias SpaceId = RoomId
|
||||
|
||||
/**
|
||||
* Value to use when no space is selected by the user.
|
||||
*/
|
||||
val MAIN_SPACE = SpaceId("!mainSpace:local")
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import io.element.android.libraries.androidutils.metadata.isInDebug
|
||||
import java.io.Serializable
|
||||
|
||||
@JvmInline
|
||||
value class ThreadId(val value: String) : Serializable {
|
||||
init {
|
||||
if (isInDebug && !MatrixPatterns.isThreadId(value)) {
|
||||
error(
|
||||
"`$value` is not a valid thread id.\n" +
|
||||
"Thread ids are the same as event ids.\n" +
|
||||
"Example thread id: `\$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg`."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
}
|
||||
|
||||
fun ThreadId.asEventId(): EventId = EventId(value)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
@JvmInline
|
||||
value class TransactionId(val value: String) : Serializable {
|
||||
override fun toString(): String = value
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
@JvmInline
|
||||
value class UniqueId(val value: String) : Serializable {
|
||||
override fun toString(): String = value
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import io.element.android.libraries.androidutils.metadata.isInDebug
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* A [String] holding a valid Matrix user ID.
|
||||
*
|
||||
* https://spec.matrix.org/v1.8/appendices/#user-identifiers
|
||||
*/
|
||||
@JvmInline
|
||||
value class UserId(val value: String) : Serializable {
|
||||
init {
|
||||
if (isInDebug && !MatrixPatterns.isUserId(value)) {
|
||||
error("`$value` is not a valid user id.\nExample user id: `@name:domain`.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
|
||||
val extractedDisplayName: String
|
||||
get() = value
|
||||
.removePrefix("@")
|
||||
.substringBefore(":")
|
||||
|
||||
val domainName: String?
|
||||
get() = value.substringAfter(":").takeIf { it.isNotEmpty() }
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.createroom
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
||||
import java.util.Optional
|
||||
|
||||
data class CreateRoomParameters(
|
||||
val name: String?,
|
||||
val topic: String? = null,
|
||||
val isEncrypted: Boolean,
|
||||
val isDirect: Boolean = false,
|
||||
val visibility: RoomVisibility,
|
||||
val preset: RoomPreset,
|
||||
val invite: List<UserId>? = null,
|
||||
val avatar: String? = null,
|
||||
val joinRuleOverride: JoinRule? = null,
|
||||
val historyVisibilityOverride: RoomHistoryVisibility? = null,
|
||||
val roomAliasName: Optional<String> = Optional.empty(),
|
||||
)
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
package io.element.android.libraries.matrix.api.createroom
|
||||
|
||||
enum class RoomPreset {
|
||||
PRIVATE_CHAT,
|
||||
PUBLIC_CHAT,
|
||||
TRUSTED_PRIVATE_CHAT,
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.encryption
|
||||
|
||||
enum class BackupState {
|
||||
/**
|
||||
* Special value, when the SDK is waiting for the first sync to be done.
|
||||
*/
|
||||
WAITING_FOR_SYNC,
|
||||
|
||||
/**
|
||||
* Values mapped from the SDK.
|
||||
*/
|
||||
UNKNOWN,
|
||||
CREATING,
|
||||
ENABLING,
|
||||
RESUMING,
|
||||
ENABLED,
|
||||
DOWNLOADING,
|
||||
DISABLING
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.encryption
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
sealed interface BackupUploadState {
|
||||
data object Unknown : BackupUploadState
|
||||
|
||||
data object Waiting : BackupUploadState
|
||||
|
||||
data class Uploading(
|
||||
val backedUpCount: Int,
|
||||
val totalCount: Int,
|
||||
) : BackupUploadState
|
||||
|
||||
data object Done : BackupUploadState
|
||||
|
||||
data object Error : BackupUploadState
|
||||
|
||||
data class SteadyException(val exception: SteadyStateException) : BackupUploadState
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.encryption
|
||||
|
||||
sealed interface EnableRecoveryProgress {
|
||||
data object Starting : EnableRecoveryProgress
|
||||
data object CreatingBackup : EnableRecoveryProgress
|
||||
data object CreatingRecoveryKey : EnableRecoveryProgress
|
||||
data class BackingUp(val backedUpCount: Int, val totalCount: Int) : EnableRecoveryProgress
|
||||
data object RoomKeyUploadError : EnableRecoveryProgress
|
||||
data class Done(val recoveryKey: String) : EnableRecoveryProgress
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.encryption
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface EncryptionService {
|
||||
val backupStateStateFlow: StateFlow<BackupState>
|
||||
val recoveryStateStateFlow: StateFlow<RecoveryState>
|
||||
val enableRecoveryProgressStateFlow: StateFlow<EnableRecoveryProgress>
|
||||
val isLastDevice: StateFlow<Boolean>
|
||||
val hasDevicesToVerifyAgainst: StateFlow<AsyncData<Boolean>>
|
||||
|
||||
suspend fun enableBackups(): Result<Unit>
|
||||
|
||||
/**
|
||||
* Enable recovery. Observe enableProgressStateFlow to get progress and recovery key.
|
||||
*/
|
||||
suspend fun enableRecovery(waitForBackupsToUpload: Boolean): Result<Unit>
|
||||
|
||||
/**
|
||||
* Change the recovery and return the new recovery key.
|
||||
*/
|
||||
suspend fun resetRecoveryKey(): Result<String>
|
||||
|
||||
suspend fun disableRecovery(): Result<Unit>
|
||||
|
||||
suspend fun doesBackupExistOnServer(): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Note: accept both recoveryKey and passphrase.
|
||||
*/
|
||||
suspend fun recover(recoveryKey: String): Result<Unit>
|
||||
|
||||
/**
|
||||
* Wait for backup upload steady state.
|
||||
*/
|
||||
fun waitForBackupUploadSteadyState(): Flow<BackupUploadState>
|
||||
|
||||
/**
|
||||
* Get the public curve25519 key of our own device in base64. This is usually what is
|
||||
* called the identity key of the device.
|
||||
*/
|
||||
suspend fun deviceCurve25519(): String?
|
||||
|
||||
/**
|
||||
* Get the public ed25519 key of our own device. This is usually what is
|
||||
* called the fingerprint of the device.
|
||||
*/
|
||||
suspend fun deviceEd25519(): String?
|
||||
|
||||
/**
|
||||
* Starts the identity reset process. This will return a handle that can be used to reset the identity.
|
||||
*/
|
||||
suspend fun startIdentityReset(): Result<IdentityResetHandle?>
|
||||
|
||||
/**
|
||||
* Remember this identity, ensuring it does not result in a pin violation.
|
||||
*/
|
||||
suspend fun pinUserIdentity(userId: UserId): Result<Unit>
|
||||
|
||||
/**
|
||||
* Withdraw the verification for that user (also pin the identity).
|
||||
*
|
||||
* Useful when a user that was verified is not anymore, but it is not
|
||||
* possible to re-verify immediately. This allows to restore communication by reverting the
|
||||
* user trust from verified to TOFU verified.
|
||||
*/
|
||||
suspend fun withdrawVerification(userId: UserId): Result<Unit>
|
||||
|
||||
/**
|
||||
* Get the identity state of a user, if known.
|
||||
* @param userId the user id to get the identity for.
|
||||
* @param fallbackToServer whether to fallback to fetching the identity from the server if not known locally. Defaults to true.
|
||||
*/
|
||||
suspend fun getUserIdentity(userId: UserId, fallbackToServer: Boolean = true): Result<IdentityState?>
|
||||
}
|
||||
|
||||
/**
|
||||
* A handle to reset the user's identity.
|
||||
*/
|
||||
sealed interface IdentityResetHandle {
|
||||
/**
|
||||
* Cancel the reset process and drops the existing handle in the SDK.
|
||||
*/
|
||||
suspend fun cancel()
|
||||
}
|
||||
|
||||
/**
|
||||
* A handle to reset the user's identity with a password login type.
|
||||
*/
|
||||
interface IdentityPasswordResetHandle : IdentityResetHandle {
|
||||
/**
|
||||
* Reset the password of the user.
|
||||
*
|
||||
* This method will block the coroutine it's running on and keep polling indefinitely until either the coroutine is cancelled, the [cancel] method is
|
||||
* called, or the identity is reset.
|
||||
*
|
||||
* @param password the current password, which will be validated before the process takes place.
|
||||
*/
|
||||
suspend fun resetPassword(password: String): Result<Unit>
|
||||
}
|
||||
|
||||
/**
|
||||
* A handle to reset the user's identity with an OIDC login type.
|
||||
*/
|
||||
interface IdentityOidcResetHandle : IdentityResetHandle {
|
||||
/**
|
||||
* The URL to open in a webview/custom tab to reset the identity.
|
||||
*/
|
||||
val url: String
|
||||
|
||||
/**
|
||||
* Reset the identity using the OIDC flow.
|
||||
*
|
||||
* This method will block the coroutine it's running on and keep polling indefinitely until either the coroutine is cancelled, the [cancel] method is
|
||||
* called, or the identity is reset.
|
||||
*/
|
||||
suspend fun resetOidc(): Result<Unit>
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
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)
|
||||
class Import(message: String) : RecoveryException(message)
|
||||
data object BackupExistsOnServer : RecoveryException("BackupExistsOnServer")
|
||||
data class Client(val exception: ClientException) : RecoveryException(exception.message ?: "Unknown error")
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.encryption
|
||||
|
||||
enum class RecoveryState {
|
||||
/**
|
||||
* Special value, when the SDK is waiting for the first sync to be done.
|
||||
*/
|
||||
WAITING_FOR_SYNC,
|
||||
|
||||
/**
|
||||
* Values mapped from the SDK.
|
||||
*/
|
||||
UNKNOWN,
|
||||
ENABLED,
|
||||
DISABLED,
|
||||
INCOMPLETE,
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.encryption
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
sealed interface SteadyStateException {
|
||||
/**
|
||||
* The backup can be deleted.
|
||||
*/
|
||||
data class BackupDisabled(val message: String) : SteadyStateException
|
||||
|
||||
/**
|
||||
* The task waiting for notifications coming from the upload task can fall behind so much that it lost some notifications.
|
||||
*/
|
||||
data class Lagged(val message: String) : SteadyStateException
|
||||
|
||||
/**
|
||||
* The request(s) to upload the room keys failed.
|
||||
*/
|
||||
data class Connection(val message: String) : SteadyStateException
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.encryption.identity
|
||||
|
||||
enum class IdentityState {
|
||||
/** The user is verified with us. */
|
||||
Verified,
|
||||
|
||||
/**
|
||||
* Either this is the first identity we have seen for this user, or the
|
||||
* user has acknowledged a change of identity explicitly e.g. by
|
||||
* clicking OK on a notification.
|
||||
*/
|
||||
Pinned,
|
||||
|
||||
/**
|
||||
* The user's identity has changed since it was pinned. The user should be
|
||||
* notified about this and given the opportunity to acknowledge the
|
||||
* change, which will make the new identity pinned.
|
||||
*/
|
||||
PinViolation,
|
||||
|
||||
/**
|
||||
* The user's identity has changed, and before that it was verified. This
|
||||
* is a serious problem. The user can either verify again to make this
|
||||
* identity verified, or withdraw verification to make it pinned.
|
||||
*/
|
||||
VerificationViolation,
|
||||
}
|
||||
|
||||
fun IdentityState.isAViolation() = this == IdentityState.PinViolation || this == IdentityState.VerificationViolation
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.encryption.identity
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
data class IdentityStateChange(
|
||||
val userId: UserId,
|
||||
val identityState: IdentityState,
|
||||
)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.exception
|
||||
|
||||
sealed class ClientException(message: String, val details: String?) : Exception(message) {
|
||||
class Generic(message: String, details: String?) : ClientException(message, details)
|
||||
class MatrixApi(val kind: ErrorKind, val code: String, message: String, details: String?) : ClientException(message, details)
|
||||
class Other(message: String) : ClientException(message, null)
|
||||
}
|
||||
|
||||
fun ClientException.isNetworkError(): Boolean {
|
||||
return this is ClientException.Generic && message?.contains("error sending request for url", ignoreCase = true) == true
|
||||
}
|
||||
+458
@@ -0,0 +1,458 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.exception
|
||||
|
||||
sealed interface ErrorKind {
|
||||
/**
|
||||
* M_BAD_ALIAS
|
||||
*
|
||||
* One or more room aliases within the m.room.canonical_alias event do
|
||||
* not point to the room ID for which the state event is to be sent to.
|
||||
*
|
||||
* room aliases: https://spec.matrix.org/latest/client-server-api/#room-aliases
|
||||
*/
|
||||
data object BadAlias : ErrorKind
|
||||
|
||||
/**
|
||||
* M_BAD_JSON
|
||||
*
|
||||
* The request contained valid JSON, but it was malformed in some way, e.g.
|
||||
* missing required keys, invalid values for keys.
|
||||
*/
|
||||
data object BadJson : ErrorKind
|
||||
|
||||
/**
|
||||
* M_BAD_STATE
|
||||
*
|
||||
* The state change requested cannot be performed, such as attempting to
|
||||
* unban a user who is not banned.
|
||||
*/
|
||||
data object BadState : ErrorKind
|
||||
|
||||
/**
|
||||
* M_BAD_STATUS
|
||||
*
|
||||
* The application service returned a bad status.
|
||||
*/
|
||||
data class BadStatus(
|
||||
/**
|
||||
* The HTTP status code of the response.
|
||||
*/
|
||||
val status: Int?,
|
||||
/**
|
||||
* The body of the response.
|
||||
*/
|
||||
val body: String?
|
||||
) : ErrorKind
|
||||
|
||||
/**
|
||||
* M_CANNOT_LEAVE_SERVER_NOTICE_ROOM
|
||||
*
|
||||
* The user is unable to reject an invite to join the server notices
|
||||
* room.
|
||||
*
|
||||
* server notices: https://spec.matrix.org/latest/client-server-api/#server-notices
|
||||
*/
|
||||
data object CannotLeaveServerNoticeRoom : ErrorKind
|
||||
|
||||
/**
|
||||
* M_CANNOT_OVERWRITE_MEDIA
|
||||
*
|
||||
* The create_content_async endpoint was called with a media ID that
|
||||
* already has content.
|
||||
*
|
||||
*/
|
||||
data object CannotOverwriteMedia : ErrorKind
|
||||
|
||||
/**
|
||||
* M_CAPTCHA_INVALID
|
||||
*
|
||||
* The Captcha provided did not match what was expected.
|
||||
*/
|
||||
data object CaptchaInvalid : ErrorKind
|
||||
|
||||
/**
|
||||
* M_CAPTCHA_NEEDED
|
||||
*
|
||||
* A Captcha is required to complete the request.
|
||||
*/
|
||||
data object CaptchaNeeded : ErrorKind
|
||||
|
||||
/**
|
||||
* M_CONNECTION_FAILED
|
||||
*
|
||||
* The connection to the application service failed.
|
||||
*/
|
||||
data object ConnectionFailed : ErrorKind
|
||||
|
||||
/**
|
||||
* M_CONNECTION_TIMEOUT
|
||||
*
|
||||
* The connection to the application service timed out.
|
||||
*/
|
||||
data object ConnectionTimeout : ErrorKind
|
||||
|
||||
/**
|
||||
* M_DUPLICATE_ANNOTATION
|
||||
*
|
||||
* The request is an attempt to send a duplicate annotation.
|
||||
*
|
||||
* duplicate annotation: https://spec.matrix.org/latest/client-server-api/#avoiding-duplicate-annotations
|
||||
*/
|
||||
data object DuplicateAnnotation : ErrorKind
|
||||
|
||||
/**
|
||||
* M_EXCLUSIVE
|
||||
*
|
||||
* The resource being requested is reserved by an application service, or
|
||||
* the application service making the request has not created the
|
||||
* resource.
|
||||
*/
|
||||
data object Exclusive : ErrorKind
|
||||
|
||||
/**
|
||||
* M_FORBIDDEN
|
||||
*
|
||||
* Forbidden access, e.g. joining a room without permission, failed login.
|
||||
*/
|
||||
data object Forbidden : ErrorKind
|
||||
|
||||
/**
|
||||
* M_GUEST_ACCESS_FORBIDDEN
|
||||
*
|
||||
* The room or resource does not permit guests to access it.
|
||||
*
|
||||
* guests: https://spec.matrix.org/latest/client-server-api/#guest-access
|
||||
*/
|
||||
data object GuestAccessForbidden : ErrorKind
|
||||
|
||||
/**
|
||||
* M_INCOMPATIBLE_ROOM_VERSION
|
||||
*
|
||||
* The client attempted to join a room that has a version the server does
|
||||
* not support.
|
||||
*/
|
||||
data class IncompatibleRoomVersion(
|
||||
/**
|
||||
* The room's version.
|
||||
*/
|
||||
val roomVersion: String
|
||||
) : ErrorKind
|
||||
|
||||
/**
|
||||
* M_INVALID_PARAM
|
||||
*
|
||||
* A parameter that was specified has the wrong value. For example, the
|
||||
* server expected an integer and instead received a string.
|
||||
*/
|
||||
data object InvalidParam : ErrorKind
|
||||
|
||||
/**
|
||||
* M_INVALID_ROOM_STATE
|
||||
*
|
||||
* The initial state implied by the parameters to the create_room
|
||||
* request is invalid, e.g. the user's power_level is set below that
|
||||
* necessary to set the room name.
|
||||
*
|
||||
*/
|
||||
data object InvalidRoomState : ErrorKind
|
||||
|
||||
/**
|
||||
* M_INVALID_USERNAME
|
||||
*
|
||||
* The desired user name is not valid.
|
||||
*/
|
||||
data object InvalidUsername : ErrorKind
|
||||
|
||||
/**
|
||||
* M_LIMIT_EXCEEDED
|
||||
*
|
||||
* The request has been refused due to rate limiting: too many requests
|
||||
* have been sent in a short period of time.
|
||||
*
|
||||
* rate limiting: https://spec.matrix.org/latest/client-server-api/#rate-limiting
|
||||
*/
|
||||
data class LimitExceeded(
|
||||
/**
|
||||
* How long a client should wait before they can try again.
|
||||
*/
|
||||
val retryAfterMs: Long?
|
||||
) : ErrorKind
|
||||
|
||||
/**
|
||||
* M_MISSING_PARAM
|
||||
*
|
||||
* A required parameter was missing from the request.
|
||||
*/
|
||||
data object MissingParam : ErrorKind
|
||||
|
||||
/**
|
||||
* M_MISSING_TOKEN
|
||||
*
|
||||
* No access token was specified for the request, but one is required.
|
||||
*
|
||||
* access token: https://spec.matrix.org/latest/client-server-api/#client-authentication
|
||||
*/
|
||||
data object MissingToken : ErrorKind
|
||||
|
||||
/**
|
||||
* M_NOT_FOUND
|
||||
*
|
||||
* No resource was found for this request.
|
||||
*/
|
||||
data object NotFound : ErrorKind
|
||||
|
||||
/**
|
||||
* M_NOT_JSON
|
||||
*
|
||||
* The request did not contain valid JSON.
|
||||
*/
|
||||
data object NotJson : ErrorKind
|
||||
|
||||
/**
|
||||
* M_NOT_YET_UPLOADED
|
||||
*
|
||||
* An mxc URI generated was used and the content is not yet available.
|
||||
*
|
||||
*/
|
||||
data object NotYetUploaded : ErrorKind
|
||||
|
||||
/**
|
||||
* M_RESOURCE_LIMIT_EXCEEDED
|
||||
*
|
||||
* The request cannot be completed because the homeserver has reached a
|
||||
* resource limit imposed on it. For example, a homeserver held in a
|
||||
* shared hosting environment may reach a resource limit if it starts
|
||||
* using too much memory or disk space.
|
||||
*/
|
||||
data class ResourceLimitExceeded(
|
||||
/**
|
||||
* A URI giving a contact method for the server administrator.
|
||||
*/
|
||||
val adminContact: String
|
||||
) : ErrorKind
|
||||
|
||||
/**
|
||||
* M_ROOM_IN_USE
|
||||
*
|
||||
* The room alias specified in the request is already taken.
|
||||
*
|
||||
* room alias: https://spec.matrix.org/latest/client-server-api/#room-aliases
|
||||
*/
|
||||
data object RoomInUse : ErrorKind
|
||||
|
||||
/**
|
||||
* M_SERVER_NOT_TRUSTED
|
||||
*
|
||||
* The client's request used a third-party server, e.g. identity server,
|
||||
* that this server does not trust.
|
||||
*/
|
||||
data object ServerNotTrusted : ErrorKind
|
||||
|
||||
/**
|
||||
* M_THREEPID_AUTH_FAILED
|
||||
*
|
||||
* Authentication could not be performed on the third-party identifier.
|
||||
*
|
||||
* third-party identifier: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
|
||||
*/
|
||||
data object ThreepidAuthFailed : ErrorKind
|
||||
|
||||
/**
|
||||
* M_THREEPID_DENIED
|
||||
*
|
||||
* The server does not permit this third-party identifier. This may
|
||||
* happen if the server only permits, for example, email addresses from
|
||||
* a particular domain.
|
||||
*
|
||||
* third-party identifier: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
|
||||
*/
|
||||
data object ThreepidDenied : ErrorKind
|
||||
|
||||
/**
|
||||
* M_THREEPID_IN_USE
|
||||
*
|
||||
* The third-party identifier is already in use by another user.
|
||||
*
|
||||
* third-party identifier: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
|
||||
*/
|
||||
data object ThreepidInUse : ErrorKind
|
||||
|
||||
/**
|
||||
* M_THREEPID_MEDIUM_NOT_SUPPORTED
|
||||
*
|
||||
* The homeserver does not support adding a third-party identifier of the
|
||||
* given medium.
|
||||
*
|
||||
* third-party identifier: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
|
||||
*/
|
||||
data object ThreepidMediumNotSupported : ErrorKind
|
||||
|
||||
/**
|
||||
* M_THREEPID_NOT_FOUND
|
||||
*
|
||||
* No account matching the given third-party identifier could be found.
|
||||
*
|
||||
* third-party identifier: https://spec.matrix.org/latest/client-server-api/#adding-account-administrative-contact-information
|
||||
*/
|
||||
data object ThreepidNotFound : ErrorKind
|
||||
|
||||
/**
|
||||
* M_TOO_LARGE
|
||||
*
|
||||
* The request or entity was too large.
|
||||
*/
|
||||
data object TooLarge : ErrorKind
|
||||
|
||||
/**
|
||||
* M_UNABLE_TO_AUTHORISE_JOIN
|
||||
*
|
||||
* The room is restricted and none of the conditions can be validated by
|
||||
* the homeserver. This can happen if the homeserver does not know
|
||||
* about any of the rooms listed as conditions, for example.
|
||||
*
|
||||
* restricted: https://spec.matrix.org/latest/client-server-api/#restricted-rooms
|
||||
*/
|
||||
data object UnableToAuthorizeJoin : ErrorKind
|
||||
|
||||
/**
|
||||
* M_UNABLE_TO_GRANT_JOIN
|
||||
*
|
||||
* A different server should be attempted for the join. This is typically
|
||||
* because the resident server can see that the joining user satisfies
|
||||
* one or more conditions, such as in the case of restricted rooms,
|
||||
* but the resident server would be unable to meet the authorization
|
||||
* rules.
|
||||
*
|
||||
* restricted rooms: https://spec.matrix.org/latest/client-server-api/#restricted-rooms
|
||||
*/
|
||||
data object UnableToGrantJoin : ErrorKind
|
||||
|
||||
/**
|
||||
* M_UNAUTHORIZED
|
||||
*
|
||||
* The request was not correctly authorized. Usually due to login failures.
|
||||
*/
|
||||
data object Unauthorized : ErrorKind
|
||||
|
||||
/**
|
||||
* M_UNKNOWN
|
||||
*
|
||||
* An unknown error has occurred.
|
||||
*/
|
||||
data object Unknown : ErrorKind
|
||||
|
||||
/**
|
||||
* M_UNKNOWN_TOKEN
|
||||
*
|
||||
* The access or refresh token specified was not recognized.
|
||||
*
|
||||
* access or refresh token: https://spec.matrix.org/latest/client-server-api/#client-authentication
|
||||
*/
|
||||
data class UnknownToken(
|
||||
/**
|
||||
* If this is true, the client is in a "soft logout" state, i.e.
|
||||
* the server requires re-authentication but the session is not
|
||||
* invalidated. The client can acquire a new access token by
|
||||
* specifying the device ID it is already using to the login API.
|
||||
*
|
||||
* soft logout: https://spec.matrix.org/latest/client-server-api/#soft-logout
|
||||
*/
|
||||
val softLogout: Boolean
|
||||
) : ErrorKind
|
||||
|
||||
/**
|
||||
* M_UNRECOGNIZED
|
||||
*
|
||||
* The server did not understand the request.
|
||||
*
|
||||
* This is expected to be returned with a 404 HTTP status code if the
|
||||
* endpoint is not implemented or a 405 HTTP status code if the
|
||||
* endpoint is implemented, but the incorrect HTTP method is used.
|
||||
*/
|
||||
data object Unrecognized : ErrorKind
|
||||
|
||||
/**
|
||||
* M_UNSUPPORTED_ROOM_VERSION
|
||||
*
|
||||
* The request to create_room used a room version that the server does
|
||||
* not support.
|
||||
*
|
||||
*/
|
||||
data object UnsupportedRoomVersion : ErrorKind
|
||||
|
||||
/**
|
||||
* M_URL_NOT_SET
|
||||
*
|
||||
* The application service doesn't have a URL configured.
|
||||
*/
|
||||
data object UrlNotSet : ErrorKind
|
||||
|
||||
/**
|
||||
* M_USER_DEACTIVATED
|
||||
*
|
||||
* The user ID associated with the request has been deactivated.
|
||||
*/
|
||||
data object UserDeactivated : ErrorKind
|
||||
|
||||
/**
|
||||
* M_USER_IN_USE
|
||||
*
|
||||
* The desired user ID is already taken.
|
||||
*/
|
||||
data object UserInUse : ErrorKind
|
||||
|
||||
/**
|
||||
* M_USER_LOCKED
|
||||
*
|
||||
* The account has been locked and cannot be used at this time.
|
||||
*
|
||||
* locked: https://spec.matrix.org/latest/client-server-api/#account-locking
|
||||
*/
|
||||
data object UserLocked : ErrorKind
|
||||
|
||||
/**
|
||||
* M_USER_SUSPENDED
|
||||
*
|
||||
* The account has been suspended and can only be used for limited
|
||||
* actions at this time.
|
||||
*
|
||||
* suspended: https://spec.matrix.org/latest/client-server-api/#account-suspension
|
||||
*/
|
||||
data object UserSuspended : ErrorKind
|
||||
|
||||
/**
|
||||
* M_WEAK_PASSWORD
|
||||
*
|
||||
* The password was rejected by the server for being too weak.
|
||||
*
|
||||
* rejected: https://spec.matrix.org/latest/client-server-api/#notes-on-password-management
|
||||
*/
|
||||
data object WeakPassword : ErrorKind
|
||||
|
||||
/**
|
||||
* M_WRONG_ROOM_KEYS_VERSION
|
||||
*
|
||||
* The version of the room keys backup provided in the request does not
|
||||
* match the current backup version.
|
||||
*
|
||||
* room keys backup: https://spec.matrix.org/latest/client-server-api/#server-side-key-backups
|
||||
*/
|
||||
data class WrongRoomKeysVersion(
|
||||
/**
|
||||
* The currently active backup version.
|
||||
*/
|
||||
val currentVersion: String?
|
||||
) : ErrorKind
|
||||
|
||||
/**
|
||||
* A custom API error.
|
||||
*/
|
||||
data class Custom(val errcode: String) : ErrorKind
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.exception
|
||||
|
||||
/**
|
||||
* Exceptions that can occur while resolving the events associated to push notifications.
|
||||
*/
|
||||
sealed class NotificationResolverException : Exception() {
|
||||
/**
|
||||
* The event was not found by the notification service.
|
||||
*/
|
||||
data object EventNotFound : NotificationResolverException()
|
||||
|
||||
/**
|
||||
* The event was found but it was filtered out by the notification service.
|
||||
*/
|
||||
data object EventFilteredOut : NotificationResolverException()
|
||||
|
||||
/**
|
||||
* An unexpected error occurred while trying to resolve the event.
|
||||
*/
|
||||
data class UnknownError(override val message: String) : NotificationResolverException()
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.media
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlin.time.Duration
|
||||
|
||||
data class AudioDetails(
|
||||
val duration: Duration,
|
||||
val waveform: ImmutableList<Float>,
|
||||
)
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.media
|
||||
|
||||
import kotlin.time.Duration
|
||||
|
||||
data class AudioInfo(
|
||||
val duration: Duration?,
|
||||
val size: Long?,
|
||||
val mimetype: String?,
|
||||
)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.media
|
||||
|
||||
data class FileInfo(
|
||||
val mimetype: String?,
|
||||
val size: Long?,
|
||||
val thumbnailInfo: ThumbnailInfo?,
|
||||
val thumbnailSource: MediaSource?
|
||||
)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.media
|
||||
|
||||
data class ImageInfo(
|
||||
val height: Long?,
|
||||
val width: Long?,
|
||||
val mimetype: String?,
|
||||
val size: Long?,
|
||||
val thumbnailInfo: ThumbnailInfo?,
|
||||
val thumbnailSource: MediaSource?,
|
||||
val blurhash: String?
|
||||
)
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.media
|
||||
|
||||
interface MatrixMediaLoader {
|
||||
/**
|
||||
* @param source to fetch the content for.
|
||||
* @return a [Result] of ByteArray. It contains the binary data for the media.
|
||||
*/
|
||||
suspend fun loadMediaContent(source: MediaSource): Result<ByteArray>
|
||||
|
||||
/**
|
||||
* @param source to fetch the data for.
|
||||
* @param width: the desired width for rescaling the media as thumbnail
|
||||
* @param height: the desired height for rescaling the media as thumbnail
|
||||
* @return a [Result] of ByteArray. It contains the binary data for the media.
|
||||
*/
|
||||
suspend fun loadMediaThumbnail(source: MediaSource, width: Long, height: Long): Result<ByteArray>
|
||||
|
||||
/**
|
||||
* @param source to fetch the data for.
|
||||
* @param mimeType: optional mime type.
|
||||
* @param filename: optional String which will be used to name the file.
|
||||
* @param useCache: if true, the rust sdk will cache the media in its store.
|
||||
* @return a [Result] of [MediaFile]
|
||||
*/
|
||||
suspend fun downloadMediaFile(
|
||||
source: MediaSource,
|
||||
mimeType: String?,
|
||||
filename: String?,
|
||||
useCache: Boolean = true,
|
||||
): Result<MediaFile>
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.media
|
||||
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* A wrapper around a media file on the disk.
|
||||
* When closed the file will be removed from the disk unless [persist] has been used.
|
||||
*/
|
||||
interface MediaFile : Closeable {
|
||||
fun path(): String
|
||||
|
||||
/**
|
||||
* Persists the temp file to the given path. The file will be moved to
|
||||
* the given path and won't be deleted anymore when closing the handle.
|
||||
*/
|
||||
fun persist(path: String): Boolean
|
||||
}
|
||||
|
||||
fun MediaFile.toFile(): File {
|
||||
return File(path())
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.media
|
||||
|
||||
/**
|
||||
* Configuration for media preview ie. invite avatars and timeline media.
|
||||
*/
|
||||
data class MediaPreviewConfig(
|
||||
val mediaPreviewValue: MediaPreviewValue,
|
||||
val hideInviteAvatar: Boolean,
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
* The default config if unknown (no local nor server config).
|
||||
*/
|
||||
val DEFAULT = MediaPreviewConfig(
|
||||
mediaPreviewValue = MediaPreviewValue.DEFAULT,
|
||||
hideInviteAvatar = false
|
||||
)
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.media
|
||||
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface MediaPreviewService {
|
||||
/**
|
||||
* Will fetch the media preview config from the server.
|
||||
*/
|
||||
suspend fun fetchMediaPreviewConfig(): Result<MediaPreviewConfig?>
|
||||
|
||||
/**
|
||||
* Will emit the media preview config known by the client.
|
||||
* This will emit a new value when received from sync.
|
||||
*/
|
||||
val mediaPreviewConfigFlow: StateFlow<MediaPreviewConfig>
|
||||
|
||||
/**
|
||||
* Set the media preview display policy. This will update the value on the server and update the local value when successful.
|
||||
*/
|
||||
suspend fun setMediaPreviewValue(mediaPreviewValue: MediaPreviewValue): Result<Unit>
|
||||
|
||||
/**
|
||||
* Set the invite avatars display policy. This will update the value on the server and update the local value when successful.
|
||||
*/
|
||||
suspend fun setHideInviteAvatars(hide: Boolean): Result<Unit>
|
||||
}
|
||||
|
||||
fun MediaPreviewService.getMediaPreviewValue() = mediaPreviewConfigFlow.value.mediaPreviewValue
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.media
|
||||
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewValue.Off
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewValue.On
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewValue.Private
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
|
||||
/**
|
||||
* Represents the values for media preview settings.
|
||||
* - [On] means that media preview are enabled
|
||||
* - [Off] means that media preview are disabled
|
||||
* - [Private] means that media preview are enabled only for private chats.
|
||||
*/
|
||||
enum class MediaPreviewValue {
|
||||
On,
|
||||
Off,
|
||||
Private;
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The default value if unknown (no local nor server config).
|
||||
*/
|
||||
val DEFAULT = On
|
||||
}
|
||||
}
|
||||
|
||||
fun MediaPreviewValue?.isPreviewEnabled(joinRule: JoinRule?): Boolean {
|
||||
return when (this) {
|
||||
null, On -> true
|
||||
Off -> false
|
||||
Private -> when (joinRule) {
|
||||
is JoinRule.Private,
|
||||
is JoinRule.Knock,
|
||||
is JoinRule.Invite,
|
||||
is JoinRule.Restricted,
|
||||
is JoinRule.KnockRestricted -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.media
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class MediaSource(
|
||||
/**
|
||||
* Url of the media.
|
||||
*/
|
||||
val url: String,
|
||||
/**
|
||||
* This is used to hold data for encrypted media.
|
||||
*/
|
||||
val json: String? = null,
|
||||
) : Parcelable
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.media
|
||||
|
||||
/**
|
||||
* This is an abstraction over the Rust SDK's `SendAttachmentJoinHandle` which allows us to either [await] the upload process or [cancel] it.
|
||||
*/
|
||||
interface MediaUploadHandler {
|
||||
/** Await the upload process to finish. */
|
||||
suspend fun await(): Result<Unit>
|
||||
|
||||
/** Cancel the upload process. */
|
||||
fun cancel()
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.media
|
||||
|
||||
data class ThumbnailInfo(
|
||||
val height: Long?,
|
||||
val width: Long?,
|
||||
val mimetype: String?,
|
||||
val size: Long?
|
||||
)
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.media
|
||||
|
||||
import kotlin.time.Duration
|
||||
|
||||
data class VideoInfo(
|
||||
val duration: Duration?,
|
||||
val height: Long?,
|
||||
val width: Long?,
|
||||
val mimetype: String?,
|
||||
val size: Long?,
|
||||
val thumbnailInfo: ThumbnailInfo?,
|
||||
val thumbnailSource: MediaSource?,
|
||||
val blurhash: String?
|
||||
)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.mxc
|
||||
|
||||
interface MxcTools {
|
||||
/**
|
||||
* 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?
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.notification
|
||||
|
||||
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.ThreadId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageType
|
||||
|
||||
data class NotificationData(
|
||||
val sessionId: SessionId,
|
||||
val eventId: EventId,
|
||||
val threadId: ThreadId?,
|
||||
val roomId: RoomId,
|
||||
// mxc url
|
||||
val senderAvatarUrl: String?,
|
||||
// private, must use `getDisambiguatedDisplayName`
|
||||
private val senderDisplayName: String?,
|
||||
private val senderIsNameAmbiguous: Boolean,
|
||||
val roomAvatarUrl: String?,
|
||||
val roomDisplayName: String?,
|
||||
val isDirect: Boolean,
|
||||
val isDm: Boolean,
|
||||
val isEncrypted: Boolean,
|
||||
val isNoisy: Boolean,
|
||||
val timestamp: Long,
|
||||
val content: NotificationContent,
|
||||
val hasMention: Boolean,
|
||||
) {
|
||||
fun getDisambiguatedDisplayName(userId: UserId): String = when {
|
||||
senderDisplayName.isNullOrBlank() -> userId.value
|
||||
senderIsNameAmbiguous -> "$senderDisplayName ($userId)"
|
||||
else -> senderDisplayName
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface NotificationContent {
|
||||
sealed interface MessageLike : NotificationContent {
|
||||
data object CallAnswer : MessageLike
|
||||
data class CallInvite(
|
||||
val senderId: UserId,
|
||||
) : MessageLike
|
||||
|
||||
data class RtcNotification(
|
||||
val senderId: UserId,
|
||||
val type: RtcNotificationType,
|
||||
val expirationTimestampMillis: Long
|
||||
) : MessageLike
|
||||
|
||||
data object CallHangup : MessageLike
|
||||
data object CallCandidates : MessageLike
|
||||
data object KeyVerificationReady : MessageLike
|
||||
data object KeyVerificationStart : MessageLike
|
||||
data object KeyVerificationCancel : MessageLike
|
||||
data object KeyVerificationAccept : MessageLike
|
||||
data object KeyVerificationKey : MessageLike
|
||||
data object KeyVerificationMac : MessageLike
|
||||
data object KeyVerificationDone : MessageLike
|
||||
data class ReactionContent(
|
||||
val relatedEventId: String
|
||||
) : MessageLike
|
||||
|
||||
data object RoomEncrypted : MessageLike
|
||||
data class RoomMessage(
|
||||
val senderId: UserId,
|
||||
val messageType: MessageType
|
||||
) : MessageLike
|
||||
|
||||
data class RoomRedaction(
|
||||
val redactedEventId: EventId?,
|
||||
val reason: String?,
|
||||
) : MessageLike
|
||||
|
||||
data object Sticker : MessageLike
|
||||
data class Poll(
|
||||
val senderId: UserId,
|
||||
val question: String,
|
||||
) : MessageLike
|
||||
}
|
||||
|
||||
sealed interface StateEvent : NotificationContent {
|
||||
data object PolicyRuleRoom : StateEvent
|
||||
data object PolicyRuleServer : StateEvent
|
||||
data object PolicyRuleUser : StateEvent
|
||||
data object RoomAliases : StateEvent
|
||||
data object RoomAvatar : StateEvent
|
||||
data object RoomCanonicalAlias : StateEvent
|
||||
data object RoomCreate : StateEvent
|
||||
data object RoomEncryption : StateEvent
|
||||
data object RoomGuestAccess : StateEvent
|
||||
data object RoomHistoryVisibility : StateEvent
|
||||
data object RoomJoinRules : StateEvent
|
||||
data class RoomMemberContent(
|
||||
val userId: UserId,
|
||||
val membershipState: RoomMembershipState
|
||||
) : StateEvent
|
||||
|
||||
data object RoomName : StateEvent
|
||||
data object RoomPinnedEvents : StateEvent
|
||||
data object RoomPowerLevels : StateEvent
|
||||
data object RoomServerAcl : StateEvent
|
||||
data object RoomThirdPartyInvite : StateEvent
|
||||
data object RoomTombstone : StateEvent
|
||||
data class RoomTopic(val topic: String) : StateEvent
|
||||
data object SpaceChild : StateEvent
|
||||
data object SpaceParent : StateEvent
|
||||
}
|
||||
|
||||
data class Invite(
|
||||
val senderId: UserId,
|
||||
) : NotificationContent
|
||||
}
|
||||
|
||||
enum class RtcNotificationType {
|
||||
RING,
|
||||
NOTIFY
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.notification
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
/**
|
||||
* Represents the resolution state of an attempt to retrieve notification data for a set of event ids.
|
||||
* The outer [Result] indicates the success or failure of the setup to retrieve notifications.
|
||||
* The inner [Result] for each [EventId] in the map indicates whether the notification data was successfully retrieved or if there was an error.
|
||||
*/
|
||||
typealias GetNotificationDataResult = Result<Map<EventId, Result<NotificationData>>>
|
||||
|
||||
/**
|
||||
* Service to retrieve notifications for a given set of event ids in specific rooms.
|
||||
*/
|
||||
interface NotificationService {
|
||||
/**
|
||||
* Fetch notifications for the specified event ids in the given rooms.
|
||||
*/
|
||||
suspend fun getNotifications(ids: Map<RoomId, List<EventId>>): GetNotificationDataResult
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.notificationsettings
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationSettingsState
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
|
||||
interface NotificationSettingsService {
|
||||
/**
|
||||
* State of the current room notification settings flow ([RoomNotificationSettingsState.Unknown] if not started).
|
||||
*/
|
||||
val notificationSettingsChangeFlow: SharedFlow<Unit>
|
||||
suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result<RoomNotificationSettings>
|
||||
suspend fun getDefaultRoomNotificationMode(isEncrypted: Boolean, isOneToOne: Boolean): Result<RoomNotificationMode>
|
||||
suspend fun setDefaultRoomNotificationMode(isEncrypted: Boolean, mode: RoomNotificationMode, isOneToOne: Boolean): Result<Unit>
|
||||
suspend fun setRoomNotificationMode(roomId: RoomId, mode: RoomNotificationMode): Result<Unit>
|
||||
suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result<Unit>
|
||||
suspend fun muteRoom(roomId: RoomId): Result<Unit>
|
||||
suspend fun unmuteRoom(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result<Unit>
|
||||
suspend fun isRoomMentionEnabled(): Result<Boolean>
|
||||
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<RoomId>>
|
||||
suspend fun canHomeServerPushEncryptedEventsToDevice(): Result<Boolean>
|
||||
suspend fun getRawPushRules(): Result<String?>
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.oidc
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||
|
||||
sealed interface AccountManagementAction {
|
||||
data object Profile : AccountManagementAction
|
||||
data object SessionsList : AccountManagementAction
|
||||
data class SessionView(val deviceId: DeviceId) : AccountManagementAction
|
||||
data class SessionEnd(val deviceId: DeviceId) : AccountManagementAction
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.permalink
|
||||
|
||||
import android.net.Uri
|
||||
|
||||
/**
|
||||
* Mapping of an input URI to a matrix.to compliant URI.
|
||||
*/
|
||||
interface MatrixToConverter {
|
||||
/**
|
||||
* Try to convert a URL from an element web instance or from a client permalink to a matrix.to url.
|
||||
* Examples:
|
||||
* - https://riot.im/develop/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org
|
||||
* - https://app.element.io/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org
|
||||
* - https://www.example.org/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org
|
||||
*/
|
||||
fun convert(uri: Uri): Uri?
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.permalink
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
interface PermalinkBuilder {
|
||||
fun permalinkForUser(userId: UserId): Result<String>
|
||||
fun permalinkForRoomAlias(roomAlias: RoomAlias): Result<String>
|
||||
}
|
||||
|
||||
sealed class PermalinkBuilderError : Throwable() {
|
||||
data object InvalidData : PermalinkBuilderError()
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.permalink
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Immutable
|
||||
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.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* This sealed class represents all the permalink cases.
|
||||
* You don't have to instantiate yourself but should use [PermalinkParser] instead.
|
||||
*/
|
||||
@Immutable
|
||||
@Parcelize
|
||||
sealed interface PermalinkData : Parcelable {
|
||||
data class RoomLink(
|
||||
val roomIdOrAlias: RoomIdOrAlias,
|
||||
val eventId: EventId? = null,
|
||||
val threadId: ThreadId? = null,
|
||||
val viaParameters: ImmutableList<String> = persistentListOf()
|
||||
) : PermalinkData
|
||||
|
||||
/*
|
||||
* &room_name=Team2
|
||||
* &room_avatar_url=mxc:
|
||||
* &inviter_name=bob
|
||||
*/
|
||||
data class RoomEmailInviteLink(
|
||||
val roomId: RoomId,
|
||||
val email: String,
|
||||
val signUrl: String,
|
||||
val roomName: String?,
|
||||
val roomAvatarUrl: String?,
|
||||
val inviterName: String?,
|
||||
val identityServer: String,
|
||||
val token: String,
|
||||
val privateKey: String,
|
||||
val roomType: String?
|
||||
) : PermalinkData
|
||||
|
||||
data class UserLink(val userId: UserId) : PermalinkData
|
||||
|
||||
data class FallbackLink(val uri: Uri, val isLegacyGroupLink: Boolean = false) : PermalinkData
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.permalink
|
||||
|
||||
/**
|
||||
* This class turns a uri to a [PermalinkData].
|
||||
* element-based domains (e.g. https://app.element.io/#/user/@chagai95:matrix.org) permalinks
|
||||
* or matrix.to permalinks (e.g. https://matrix.to/#/@chagai95:matrix.org)
|
||||
* or client permalinks (e.g. <clientPermalinkBaseUrl>user/@chagai95:matrix.org)
|
||||
* or matrix: permalinks (e.g. matrix:u/chagai95:matrix.org)
|
||||
*/
|
||||
interface PermalinkParser {
|
||||
/**
|
||||
* Turns a uri string to a [PermalinkData].
|
||||
* https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md
|
||||
*/
|
||||
fun parse(uriString: String): PermalinkData
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.platform
|
||||
|
||||
import io.element.android.libraries.matrix.api.tracing.TracingConfiguration
|
||||
|
||||
/**
|
||||
* This service is responsible for initializing the platform-related settings of the SDK.
|
||||
*/
|
||||
interface InitPlatformService {
|
||||
/**
|
||||
* Initialize the platform-related settings of the SDK.
|
||||
* @param tracingConfiguration the tracing configuration to use for logging.
|
||||
*/
|
||||
fun init(tracingConfiguration: TracingConfiguration)
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.poll
|
||||
|
||||
data class PollAnswer(
|
||||
val id: String,
|
||||
val text: String
|
||||
)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
package io.element.android.libraries.matrix.api.poll
|
||||
|
||||
enum class PollKind {
|
||||
/** Voters should see results as soon as they have voted. */
|
||||
Disclosed,
|
||||
|
||||
/** Results should be only revealed when the poll is ended. */
|
||||
Undisclosed,
|
||||
}
|
||||
|
||||
val PollKind.isDisclosed: Boolean
|
||||
get() = this == PollKind.Disclosed
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.pusher
|
||||
|
||||
interface PushersService {
|
||||
suspend fun setHttpPusher(setHttpPusherData: SetHttpPusherData): Result<Unit>
|
||||
suspend fun unsetHttpPusher(unsetHttpPusherData: UnsetHttpPusherData): Result<Unit>
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.pusher
|
||||
|
||||
data class SetHttpPusherData(
|
||||
val pushKey: String,
|
||||
val appId: String,
|
||||
val url: String,
|
||||
val appDisplayName: String,
|
||||
val deviceDisplayName: String,
|
||||
val profileTag: String?,
|
||||
val lang: String,
|
||||
val defaultPayload: String,
|
||||
)
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.pusher
|
||||
|
||||
data class UnsetHttpPusherData(
|
||||
val pushKey: String,
|
||||
val appId: String,
|
||||
)
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
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.ThreadId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.io.Closeable
|
||||
|
||||
/**
|
||||
* This interface represents the common functionality for a local room, whether it's joined, invited, knocked, or left.
|
||||
*/
|
||||
interface BaseRoom : Closeable {
|
||||
/**
|
||||
* The session id of the current user.
|
||||
*/
|
||||
val sessionId: SessionId
|
||||
|
||||
/**
|
||||
* The id of the room.
|
||||
*/
|
||||
val roomId: RoomId
|
||||
|
||||
/**
|
||||
* The coroutine scope that will handle all jobs related to this room.
|
||||
*/
|
||||
val roomCoroutineScope: CoroutineScope
|
||||
|
||||
/**
|
||||
* The current loaded members as a StateFlow.
|
||||
* Initial value is [RoomMembersState.Unknown].
|
||||
* To update them you should call [updateMembers].
|
||||
*/
|
||||
val membersStateFlow: StateFlow<RoomMembersState>
|
||||
|
||||
/**
|
||||
* A flow that emits the current [RoomInfo] state.
|
||||
*/
|
||||
val roomInfoFlow: StateFlow<RoomInfo>
|
||||
|
||||
/**
|
||||
* Get the latest room info we have received from the SDK stream.
|
||||
*/
|
||||
fun info(): RoomInfo = roomInfoFlow.value
|
||||
|
||||
fun predecessorRoom(): PredecessorRoom?
|
||||
|
||||
/**
|
||||
* A one-to-one is a room with exactly 2 members.
|
||||
* See [the Matrix spec](https://spec.matrix.org/latest/client-server-api/#default-underride-rules).
|
||||
*/
|
||||
val isOneToOne: Boolean get() = info().activeMembersCount == 2L
|
||||
|
||||
/**
|
||||
* Try to load the room members and update the membersFlow.
|
||||
*/
|
||||
suspend fun updateMembers()
|
||||
|
||||
/**
|
||||
* Get the members of the room. Note: generally this should not be used, please use
|
||||
* [membersStateFlow] and [updateMembers] instead.
|
||||
*/
|
||||
suspend fun getMembers(limit: Int = 5): Result<List<RoomMember>>
|
||||
|
||||
/**
|
||||
* Will return an updated member or an error.
|
||||
*/
|
||||
suspend fun getUpdatedMember(userId: UserId): Result<RoomMember>
|
||||
|
||||
/**
|
||||
* Adds the room to the sync subscription list.
|
||||
*/
|
||||
suspend fun subscribeToSync()
|
||||
|
||||
/**
|
||||
* Gets the power levels of the room.
|
||||
*/
|
||||
suspend fun powerLevels(): Result<RoomPowerLevelsValues>
|
||||
|
||||
/**
|
||||
* Gets the role of the user with the provided [userId] in the room.
|
||||
*/
|
||||
suspend fun userRole(userId: UserId): Result<RoomMember.Role>
|
||||
|
||||
/**
|
||||
* Gets the display name of the user with the provided [userId] in the room.
|
||||
*/
|
||||
suspend fun userDisplayName(userId: UserId): Result<String?>
|
||||
|
||||
/**
|
||||
* Gets the avatar of the user with the provided [userId] in the room.
|
||||
*/
|
||||
suspend fun userAvatarUrl(userId: UserId): Result<String?>
|
||||
|
||||
/**
|
||||
* Leaves and forgets the room. Only joined, invited or knocked rooms can be left.
|
||||
*/
|
||||
suspend fun leave(): Result<Unit>
|
||||
|
||||
/**
|
||||
* Joins the room. Only invited rooms can be joined.
|
||||
*/
|
||||
suspend fun join(): Result<Unit>
|
||||
|
||||
/**
|
||||
* Forgets about the room, removing it from the server and the local cache. Only left and banned rooms can be forgotten.
|
||||
*/
|
||||
suspend fun forget(): Result<Unit>
|
||||
|
||||
/**
|
||||
* Returns `true` if the user with the provided [userId] can invite other users to the room.
|
||||
*/
|
||||
suspend fun canUserInvite(userId: UserId): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Returns `true` if the user with the provided [userId] can kick other users from the room.
|
||||
*/
|
||||
suspend fun canUserKick(userId: UserId): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Returns `true` if the user with the provided [userId] can ban other users from the room.
|
||||
*/
|
||||
suspend fun canUserBan(userId: UserId): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Returns `true` if the user with the provided [userId] can redact their own messages.
|
||||
*/
|
||||
suspend fun canUserRedactOwn(userId: UserId): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Returns `true` if the user with the provided [userId] can redact messages from other users.
|
||||
*/
|
||||
suspend fun canUserRedactOther(userId: UserId): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Returns `true` if the user with the provided [userId] can send state events.
|
||||
*/
|
||||
suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Returns `true` if the user with the provided [userId] can send messages.
|
||||
*/
|
||||
suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Returns `true` if the user with the provided [userId] can trigger an `@room` notification.
|
||||
*/
|
||||
suspend fun canUserTriggerRoomNotification(userId: UserId): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Returns `true` if the user with the provided [userId] can pin or unpin messages.
|
||||
*/
|
||||
suspend fun canUserPinUnpin(userId: UserId): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Returns `true` if the user with the provided [userId] can join or starts calls.
|
||||
*/
|
||||
suspend fun canUserJoinCall(userId: UserId): Result<Boolean> =
|
||||
canUserSendState(userId, StateEventType.CALL_MEMBER)
|
||||
|
||||
/**
|
||||
* Sets the room as favorite or not, based on the [isFavorite] parameter.
|
||||
*/
|
||||
suspend fun setIsFavorite(isFavorite: Boolean): Result<Unit>
|
||||
|
||||
/**
|
||||
* Mark the room as read by trying to attach an unthreaded read receipt to the latest room event.
|
||||
*
|
||||
* Note this will instantiate a new timeline, which is an expensive operation.
|
||||
* Prefer using [Timeline.markAsRead] instead when possible.
|
||||
*
|
||||
* @param receiptType The type of receipt to send.
|
||||
*/
|
||||
suspend fun markAsRead(receiptType: ReceiptType): Result<Unit>
|
||||
|
||||
/**
|
||||
* Sets a flag on the room to indicate that the user has explicitly marked it as unread, or reverts the flag.
|
||||
* @param isUnread true to mark the room as unread, false to remove the flag.
|
||||
*/
|
||||
suspend fun setUnreadFlag(isUnread: Boolean): Result<Unit>
|
||||
|
||||
/**
|
||||
* Clear the event cache storage for the current room.
|
||||
*/
|
||||
suspend fun clearEventCacheStorage(): Result<Unit>
|
||||
|
||||
/**
|
||||
* Get the permalink for the room.
|
||||
*/
|
||||
suspend fun getPermalink(): Result<String>
|
||||
|
||||
/**
|
||||
* Get the permalink for the provided [eventId].
|
||||
* @param eventId The event id to get the permalink for.
|
||||
* @return The permalink, or a failure.
|
||||
*/
|
||||
suspend fun getPermalinkFor(eventId: EventId): Result<String>
|
||||
|
||||
/**
|
||||
* Returns the visibility for this room in the room directory.
|
||||
* If the room is not published, the result will be [RoomVisibility.Private].
|
||||
*/
|
||||
suspend fun getRoomVisibility(): Result<RoomVisibility>
|
||||
|
||||
/**
|
||||
* Returns the visibility for this room in the room directory, fetching it from the homeserver if needed.
|
||||
*/
|
||||
suspend fun getUpdatedIsEncrypted(): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Store the given `ComposerDraft` in the state store of this room.
|
||||
*/
|
||||
suspend fun saveComposerDraft(composerDraft: ComposerDraft, threadRoot: ThreadId?): Result<Unit>
|
||||
|
||||
/**
|
||||
* Retrieve the `ComposerDraft` stored in the state store for this room.
|
||||
*/
|
||||
suspend fun loadComposerDraft(threadRoot: ThreadId?): Result<ComposerDraft?>
|
||||
|
||||
/**
|
||||
* Clear the `ComposerDraft` stored in the state store for this room.
|
||||
*/
|
||||
suspend fun clearComposerDraft(threadRoot: ThreadId?): Result<Unit>
|
||||
|
||||
/**
|
||||
* Reports a room as inappropriate to the server.
|
||||
* The caller is not required to be joined to the room to report it.
|
||||
* @param reason - The reason the room is being reported.
|
||||
*/
|
||||
suspend fun reportRoom(reason: String?): Result<Unit>
|
||||
|
||||
suspend fun declineCall(notificationEventId: EventId): Result<Unit>
|
||||
|
||||
suspend fun subscribeToCallDecline(notificationEventId: EventId): Flow<UserId>
|
||||
|
||||
suspend fun threadRootIdForEvent(eventId: EventId): Result<ThreadId?>
|
||||
|
||||
/**
|
||||
* Destroy the room and release all resources associated to it.
|
||||
*/
|
||||
fun destroy()
|
||||
|
||||
override fun close() = destroy()
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
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.ThreadId
|
||||
|
||||
sealed interface CreateTimelineParams {
|
||||
data class Focused(val focusedEventId: EventId) : CreateTimelineParams
|
||||
data object MediaOnly : CreateTimelineParams
|
||||
data class MediaOnlyFocused(val focusedEventId: EventId) : CreateTimelineParams
|
||||
data object PinnedOnly : CreateTimelineParams
|
||||
data class Threaded(val threadRootEventId: ThreadId) : CreateTimelineParams
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
enum class CurrentUserMembership {
|
||||
INVITED,
|
||||
JOINED,
|
||||
LEFT,
|
||||
KNOCKED,
|
||||
BANNED,
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
* Method to filter members by userId or displayName.
|
||||
* It does filter through the already known members, it doesn't perform additional requests.
|
||||
*/
|
||||
suspend fun BaseRoom.filterMembers(query: String, coroutineContext: CoroutineContext): List<RoomMember> = withContext(coroutineContext) {
|
||||
val roomMembersState = membersStateFlow.value
|
||||
val activeRoomMembers = roomMembersState.roomMembers()
|
||||
?.filter { it.membership.isActive() }
|
||||
.orEmpty()
|
||||
val filteredMembers = if (query.isBlank()) {
|
||||
activeRoomMembers
|
||||
} else {
|
||||
activeRoomMembers.filter { member ->
|
||||
member.userId.value.contains(query, ignoreCase = true) ||
|
||||
member.displayName?.contains(query, ignoreCase = true).orFalse()
|
||||
}
|
||||
}
|
||||
filteredMembers
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
class ForwardEventException(
|
||||
val roomIds: List<RoomId>
|
||||
) : Exception() {
|
||||
override val message: String? = "Failed to deliver event to $roomIds rooms"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
sealed interface IntentionalMention {
|
||||
data class User(val userId: UserId) : IntentionalMention
|
||||
data object Room : IntentionalMention
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||
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.SendHandle
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityStateChange
|
||||
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface JoinedRoom : BaseRoom {
|
||||
val syncUpdateFlow: StateFlow<Long>
|
||||
|
||||
val roomTypingMembersFlow: Flow<List<UserId>>
|
||||
val identityStateChangesFlow: Flow<List<IdentityStateChange>>
|
||||
val roomNotificationSettingsStateFlow: StateFlow<RoomNotificationSettingsState>
|
||||
|
||||
/**
|
||||
* The current knock requests in the room as a Flow.
|
||||
*/
|
||||
val knockRequestsFlow: Flow<List<KnockRequest>>
|
||||
|
||||
/**
|
||||
* The live timeline of the room. Must be used to send Event to a room.
|
||||
*/
|
||||
val liveTimeline: Timeline
|
||||
|
||||
/**
|
||||
* Create a new timeline.
|
||||
* @param createTimelineParams contains parameters about how to filter the timeline. Will also configure the date separators.
|
||||
*/
|
||||
suspend fun createTimeline(
|
||||
createTimelineParams: CreateTimelineParams,
|
||||
): Result<Timeline>
|
||||
|
||||
suspend fun editMessage(eventId: EventId, body: String, htmlBody: String?, intentionalMentions: List<IntentionalMention>): Result<Unit>
|
||||
|
||||
/**
|
||||
* Send a typing notification.
|
||||
* @param isTyping True if the user is typing, false otherwise.
|
||||
*/
|
||||
suspend fun typingNotice(isTyping: Boolean): Result<Unit>
|
||||
|
||||
suspend fun inviteUserById(id: UserId): Result<Unit>
|
||||
|
||||
suspend fun updateAvatar(mimeType: String, data: ByteArray): Result<Unit>
|
||||
|
||||
suspend fun removeAvatar(): Result<Unit>
|
||||
|
||||
suspend fun updateRoomNotificationSettings(): Result<Unit>
|
||||
|
||||
/**
|
||||
* Update the canonical alias of the room.
|
||||
*
|
||||
* Note that publishing the alias in the room directory is done separately.
|
||||
*/
|
||||
suspend fun updateCanonicalAlias(
|
||||
canonicalAlias: RoomAlias?,
|
||||
alternativeAliases: List<RoomAlias>
|
||||
): Result<Unit>
|
||||
|
||||
/**
|
||||
* Update the room's visibility in the room directory.
|
||||
*/
|
||||
suspend fun updateRoomVisibility(roomVisibility: RoomVisibility): Result<Unit>
|
||||
|
||||
/**
|
||||
* Update room history visibility for this room.
|
||||
*/
|
||||
suspend fun updateHistoryVisibility(historyVisibility: RoomHistoryVisibility): Result<Unit>
|
||||
|
||||
/**
|
||||
* Publish a new room alias for this room in the room directory.
|
||||
*
|
||||
* Returns:
|
||||
* - `true` if the room alias didn't exist and it's now published.
|
||||
* - `false` if the room alias was already present so it couldn't be
|
||||
* published.
|
||||
*/
|
||||
suspend fun publishRoomAliasInRoomDirectory(roomAlias: RoomAlias): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Remove an existing room alias for this room in the room directory.
|
||||
*
|
||||
* Returns:
|
||||
* - `true` if the room alias was present and it's now removed from the
|
||||
* room directory.
|
||||
* - `false` if the room alias didn't exist so it couldn't be removed.
|
||||
*/
|
||||
suspend fun removeRoomAliasFromRoomDirectory(roomAlias: RoomAlias): Result<Boolean>
|
||||
|
||||
/**
|
||||
* Enable End-to-end encryption in this room.
|
||||
*/
|
||||
suspend fun enableEncryption(): Result<Unit>
|
||||
|
||||
/**
|
||||
* Update the join rule for this room.
|
||||
*/
|
||||
suspend fun updateJoinRule(joinRule: JoinRule): Result<Unit>
|
||||
|
||||
suspend fun updateUsersRoles(changes: List<UserRoleChange>): Result<Unit>
|
||||
|
||||
suspend fun updatePowerLevels(roomPowerLevelsValues: RoomPowerLevelsValues): Result<Unit>
|
||||
|
||||
suspend fun resetPowerLevels(): Result<Unit>
|
||||
|
||||
suspend fun setName(name: String): Result<Unit>
|
||||
|
||||
suspend fun setTopic(topic: String): Result<Unit>
|
||||
|
||||
suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result<Unit>
|
||||
|
||||
suspend fun kickUser(userId: UserId, reason: String? = null): Result<Unit>
|
||||
|
||||
suspend fun banUser(userId: UserId, reason: String? = null): Result<Unit>
|
||||
|
||||
suspend fun unbanUser(userId: UserId, reason: String? = null): Result<Unit>
|
||||
|
||||
/**
|
||||
* Generates a Widget url to display in a [android.webkit.WebView] given the provided parameters.
|
||||
* @param widgetSettings The widget settings to use.
|
||||
* @param clientId The client id to use. It should be unique per app install.
|
||||
* @param languageTag The language tag to use. If null, the default language will be used.
|
||||
* @param theme The theme to use. If null, the default theme will be used.
|
||||
* @return The resulting url, or a failure.
|
||||
*/
|
||||
suspend fun generateWidgetWebViewUrl(
|
||||
widgetSettings: MatrixWidgetSettings,
|
||||
clientId: String,
|
||||
languageTag: String?,
|
||||
theme: String?,
|
||||
): Result<String>
|
||||
|
||||
/**
|
||||
* Get a [MatrixWidgetDriver] for the provided [widgetSettings].
|
||||
* @param widgetSettings The widget settings to use.
|
||||
* @return The resulting [MatrixWidgetDriver], or a failure.
|
||||
*/
|
||||
fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result<MatrixWidgetDriver>
|
||||
|
||||
suspend fun setSendQueueEnabled(enabled: Boolean)
|
||||
|
||||
/**
|
||||
* Ignore the local trust for the given devices and resend messages that failed to send because said devices are unverified.
|
||||
*
|
||||
* @param devices The map of users identifiers to device identifiers received in the error
|
||||
* @param sendHandle The send queue handle of the local echo the send error applies to. It can be used to retry the upload.
|
||||
*
|
||||
*/
|
||||
suspend fun ignoreDeviceTrustAndResend(devices: Map<UserId, List<DeviceId>>, sendHandle: SendHandle): Result<Unit>
|
||||
|
||||
/**
|
||||
* Remove verification requirements for the given users and
|
||||
* resend messages that failed to send because their identities were no longer verified.
|
||||
*
|
||||
* @param userIds The list of users identifiers received in the error.
|
||||
* @param sendHandle The send queue handle of the local echo the send error applies to. It can be used to retry the upload.
|
||||
*
|
||||
*/
|
||||
suspend fun withdrawVerificationAndResend(userIds: List<UserId>, sendHandle: SendHandle): Result<Unit>
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
sealed interface MessageEventType {
|
||||
data object CallAnswer : MessageEventType
|
||||
data object CallInvite : MessageEventType
|
||||
data object CallHangup : MessageEventType
|
||||
data object CallCandidates : MessageEventType
|
||||
data object RtcNotification : MessageEventType
|
||||
data object KeyVerificationReady : MessageEventType
|
||||
data object KeyVerificationStart : MessageEventType
|
||||
data object KeyVerificationCancel : MessageEventType
|
||||
data object KeyVerificationAccept : MessageEventType
|
||||
data object KeyVerificationKey : MessageEventType
|
||||
data object KeyVerificationMac : MessageEventType
|
||||
data object KeyVerificationDone : MessageEventType
|
||||
data object Reaction : MessageEventType
|
||||
data object RoomEncrypted : MessageEventType
|
||||
data object RoomMessage : MessageEventType
|
||||
data object RoomRedaction : MessageEventType
|
||||
data object Sticker : MessageEventType
|
||||
data object PollEnd : MessageEventType
|
||||
data object PollResponse : MessageEventType
|
||||
data object PollStart : MessageEventType
|
||||
data object UnstablePollEnd : MessageEventType
|
||||
data object UnstablePollResponse : MessageEventType
|
||||
data object UnstablePollStart : MessageEventType
|
||||
data class Other(val type: String) : MessageEventType
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
|
||||
|
||||
/** A reference to a room either invited, knocked or banned. */
|
||||
interface NotJoinedRoom : AutoCloseable {
|
||||
val previewInfo: RoomPreviewInfo
|
||||
val localRoom: BaseRoom?
|
||||
|
||||
/**
|
||||
* Get the membership details of the user in the room, as well as from the user who sent the `m.room.member` event.
|
||||
*/
|
||||
suspend fun membershipDetails(): Result<RoomMembershipDetails?>
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
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.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class RoomInfo(
|
||||
val id: RoomId,
|
||||
/** The room's name from the room state event if received from sync, or one that's been computed otherwise. */
|
||||
val name: String?,
|
||||
/** Room name as defined by the room state event only. */
|
||||
val rawName: String?,
|
||||
val topic: String?,
|
||||
val avatarUrl: String?,
|
||||
val isPublic: Boolean?,
|
||||
val isDirect: Boolean,
|
||||
val isEncrypted: Boolean?,
|
||||
val joinRule: JoinRule?,
|
||||
val isSpace: Boolean,
|
||||
val isFavorite: Boolean,
|
||||
val canonicalAlias: RoomAlias?,
|
||||
val alternativeAliases: ImmutableList<RoomAlias>,
|
||||
val currentUserMembership: CurrentUserMembership,
|
||||
/**
|
||||
* Member who invited the current user to a room that's in the invited
|
||||
* state.
|
||||
*
|
||||
* Can be missing if the room membership invite event is missing from the
|
||||
* store.
|
||||
*/
|
||||
val inviter: RoomMember?,
|
||||
val activeMembersCount: Long,
|
||||
val invitedMembersCount: Long,
|
||||
val joinedMembersCount: Long,
|
||||
val roomPowerLevels: RoomPowerLevels?,
|
||||
val highlightCount: Long,
|
||||
val notificationCount: Long,
|
||||
val userDefinedNotificationMode: RoomNotificationMode?,
|
||||
val hasRoomCall: Boolean,
|
||||
val activeRoomCallParticipants: ImmutableList<UserId>,
|
||||
val isMarkedUnread: Boolean,
|
||||
/**
|
||||
* "Interesting" messages received in that room, independently of the
|
||||
* notification settings.
|
||||
*/
|
||||
val numUnreadMessages: Long,
|
||||
/**
|
||||
* Events that will notify the user, according to their
|
||||
* notification settings.
|
||||
*/
|
||||
val numUnreadNotifications: Long,
|
||||
/**
|
||||
* Events causing mentions/highlights for the user, according to their
|
||||
* notification settings.
|
||||
*/
|
||||
val numUnreadMentions: Long,
|
||||
val heroes: ImmutableList<MatrixUser>,
|
||||
val pinnedEventIds: ImmutableList<EventId>,
|
||||
val creators: ImmutableList<UserId>,
|
||||
val historyVisibility: RoomHistoryVisibility,
|
||||
val successorRoom: SuccessorRoom?,
|
||||
val roomVersion: String?,
|
||||
val privilegedCreatorRole: Boolean,
|
||||
) {
|
||||
val aliases: List<RoomAlias>
|
||||
get() = listOfNotNull(canonicalAlias) + alternativeAliases
|
||||
|
||||
/**
|
||||
* Returns the list of users with the given [role] in this room.
|
||||
*/
|
||||
fun usersWithRole(role: RoomMember.Role): List<UserId> {
|
||||
return if (role is RoomMember.Role.Owner && role.isCreator) {
|
||||
this.creators
|
||||
} else {
|
||||
this.roomPowerLevels?.usersWithRole(role).orEmpty().toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
/**
|
||||
* Returns whether the room with the provided info is a DM.
|
||||
* A DM is a room with at most 2 active members (one of them may have left).
|
||||
*
|
||||
* @param isDirect true if the room is direct
|
||||
* @param activeMembersCount the number of active members in the room (joined or invited)
|
||||
*/
|
||||
fun isDm(isDirect: Boolean, activeMembersCount: Int): Boolean {
|
||||
return isDirect && activeMembersCount <= 2
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the [BaseRoom] is a DM, with an updated state from the latest [RoomInfo].
|
||||
*/
|
||||
suspend fun BaseRoom.isDm() = roomInfoFlow.first().isDm
|
||||
|
||||
/**
|
||||
* Returns whether the [RoomInfo] is from a DM.
|
||||
*/
|
||||
val RoomInfo.isDm get() = isDm(isDirect, activeMembersCount.toInt())
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
||||
data class RoomMember(
|
||||
val userId: UserId,
|
||||
val displayName: String?,
|
||||
val avatarUrl: String?,
|
||||
val membership: RoomMembershipState,
|
||||
val isNameAmbiguous: Boolean,
|
||||
val powerLevel: Long,
|
||||
val isIgnored: Boolean,
|
||||
val role: Role,
|
||||
val membershipChangeReason: String?,
|
||||
) {
|
||||
/**
|
||||
* Role of the RoomMember, based on its [powerLevel].
|
||||
*/
|
||||
@Immutable
|
||||
sealed interface Role {
|
||||
data class Owner(val isCreator: Boolean) : Role
|
||||
data object Admin : Role
|
||||
data object Moderator : Role
|
||||
data object User : Role
|
||||
|
||||
val powerLevel: Long
|
||||
get() = when (this) {
|
||||
is Owner -> if (isCreator) CREATOR_POWERLEVEL else SUPERADMIN_POWERLEVEL
|
||||
Admin -> ADMIN_POWERLEVEL
|
||||
Moderator -> MODERATOR_POWERLEVEL
|
||||
User -> USER_POWERLEVEL
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CREATOR_POWERLEVEL = Long.MAX_VALUE
|
||||
private const val SUPERADMIN_POWERLEVEL = 150L
|
||||
private const val ADMIN_POWERLEVEL = 100L
|
||||
private const val MODERATOR_POWERLEVEL = 50L
|
||||
private const val USER_POWERLEVEL = 0L
|
||||
|
||||
fun forPowerLevel(powerLevel: Long): Role {
|
||||
return when {
|
||||
powerLevel == CREATOR_POWERLEVEL -> Owner(isCreator = true)
|
||||
powerLevel >= SUPERADMIN_POWERLEVEL -> Owner(isCreator = false)
|
||||
powerLevel >= ADMIN_POWERLEVEL -> Admin
|
||||
powerLevel >= MODERATOR_POWERLEVEL -> Moderator
|
||||
else -> User
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disambiguated display name for the RoomMember.
|
||||
* If the display name is null, the user ID is returned.
|
||||
* If the display name is ambiguous, the user ID is appended in parentheses.
|
||||
* Otherwise, the display name is returned.
|
||||
*/
|
||||
val disambiguatedDisplayName: String = when {
|
||||
displayName == null -> userId.value
|
||||
isNameAmbiguous -> "$displayName ($userId)"
|
||||
else -> displayName
|
||||
}
|
||||
|
||||
val displayNameOrDefault: String
|
||||
get() = when {
|
||||
displayName == null -> userId.extractedDisplayName
|
||||
else -> displayName
|
||||
}
|
||||
}
|
||||
|
||||
enum class RoomMembershipState {
|
||||
BAN,
|
||||
INVITE,
|
||||
JOIN,
|
||||
KNOCK,
|
||||
LEAVE;
|
||||
|
||||
fun isActive(): Boolean = this == JOIN || this == INVITE
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best name value to display for the RoomMember.
|
||||
* If the [RoomMember.displayName] is present and not empty it'll be used, otherwise the [RoomMember.userId] will be used.
|
||||
*/
|
||||
fun RoomMember.getBestName(): String {
|
||||
return displayName?.takeIf { it.isNotEmpty() } ?: userId.value
|
||||
}
|
||||
|
||||
fun RoomMember.toMatrixUser() = MatrixUser(
|
||||
userId = userId,
|
||||
displayName = displayName,
|
||||
avatarUrl = avatarUrl,
|
||||
)
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
sealed interface RoomMembersState {
|
||||
data object Unknown : RoomMembersState
|
||||
data class Pending(val prevRoomMembers: ImmutableList<RoomMember>? = null) : RoomMembersState
|
||||
data class Error(val failure: Throwable, val prevRoomMembers: ImmutableList<RoomMember>? = null) : RoomMembersState
|
||||
data class Ready(val roomMembers: ImmutableList<RoomMember>) : RoomMembersState
|
||||
}
|
||||
|
||||
fun RoomMembersState.roomMembers(): List<RoomMember>? {
|
||||
return when (this) {
|
||||
is RoomMembersState.Ready -> roomMembers
|
||||
is RoomMembersState.Pending -> prevRoomMembers
|
||||
is RoomMembersState.Error -> prevRoomMembers
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun RoomMembersState.joinedRoomMembers(): List<RoomMember> {
|
||||
return roomMembers().orEmpty().filter { it.membership == RoomMembershipState.JOIN }
|
||||
}
|
||||
|
||||
fun RoomMembersState.activeRoomMembers(): List<RoomMember> {
|
||||
return roomMembers().orEmpty().filter { it.membership.isActive() }
|
||||
}
|
||||
|
||||
fun RoomMembersState.getDirectRoomMember(roomInfo: RoomInfo, sessionId: SessionId): RoomMember? {
|
||||
return roomMembers()
|
||||
?.takeIf { roomInfo.isDm }
|
||||
?.find { it.userId != sessionId && it.membership.isActive() }
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
/**
|
||||
* Room membership details for the current user and the sender of the membership event.
|
||||
*
|
||||
* It also includes the reason the current user's membership changed, if any.
|
||||
*/
|
||||
data class RoomMembershipDetails(
|
||||
val currentUserMember: RoomMember,
|
||||
val senderMember: RoomMember?,
|
||||
) {
|
||||
val membershipChangeReason: String? = currentUserMember.membershipChangeReason
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
|
||||
class RoomMembershipObserver {
|
||||
data class RoomMembershipUpdate(
|
||||
val roomId: RoomId,
|
||||
val isSpace: Boolean,
|
||||
val isUserInRoom: Boolean,
|
||||
val change: MembershipChange,
|
||||
)
|
||||
|
||||
private val _updates = MutableSharedFlow<RoomMembershipUpdate>(extraBufferCapacity = 10)
|
||||
val updates = _updates.asSharedFlow()
|
||||
|
||||
suspend fun notifyUserLeftRoom(
|
||||
roomId: RoomId,
|
||||
isSpace: Boolean,
|
||||
membershipBeforeLeft: CurrentUserMembership,
|
||||
) {
|
||||
val membershipChange = when (membershipBeforeLeft) {
|
||||
CurrentUserMembership.INVITED -> MembershipChange.INVITATION_REJECTED
|
||||
CurrentUserMembership.KNOCKED -> MembershipChange.KNOCK_RETRACTED
|
||||
else -> MembershipChange.LEFT
|
||||
}
|
||||
_updates.emit(
|
||||
RoomMembershipUpdate(
|
||||
roomId = roomId,
|
||||
isSpace = isSpace,
|
||||
isUserInRoom = false,
|
||||
change = membershipChange,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
data class RoomNotificationSettings(
|
||||
val mode: RoomNotificationMode,
|
||||
val isDefault: Boolean,
|
||||
)
|
||||
|
||||
enum class RoomNotificationMode {
|
||||
ALL_MESSAGES,
|
||||
MENTIONS_AND_KEYWORDS_ONLY,
|
||||
MUTE
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
sealed interface RoomNotificationSettingsState {
|
||||
data object Unknown : RoomNotificationSettingsState
|
||||
data class Pending(val prevRoomNotificationSettings: RoomNotificationSettings? = null) : RoomNotificationSettingsState
|
||||
data class Error(val failure: Throwable, val prevRoomNotificationSettings: RoomNotificationSettings? = null) : RoomNotificationSettingsState
|
||||
data class Ready(val roomNotificationSettings: RoomNotificationSettings) : RoomNotificationSettingsState
|
||||
}
|
||||
|
||||
fun RoomNotificationSettingsState.roomNotificationSettings(): RoomNotificationSettings? {
|
||||
return when (this) {
|
||||
is RoomNotificationSettingsState.Ready -> roomNotificationSettings
|
||||
is RoomNotificationSettingsState.Pending -> prevRoomNotificationSettings
|
||||
is RoomNotificationSettingsState.Error -> prevRoomNotificationSettings
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
sealed interface RoomType {
|
||||
data object Space : RoomType
|
||||
data object Room : RoomType
|
||||
data class Other(val type: String) : RoomType
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
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 and [createIfDmDoesNotExist] is true.
|
||||
*/
|
||||
suspend fun MatrixClient.startDM(
|
||||
userId: UserId,
|
||||
createIfDmDoesNotExist: Boolean,
|
||||
): StartDMResult {
|
||||
return findDM(userId)
|
||||
.fold(
|
||||
onSuccess = { existingDM ->
|
||||
if (existingDM != null) {
|
||||
StartDMResult.Success(existingDM, isNew = false)
|
||||
} else if (createIfDmDoesNotExist) {
|
||||
createDM(userId).fold(
|
||||
{ StartDMResult.Success(it, isNew = true) },
|
||||
{ StartDMResult.Failure(it) }
|
||||
)
|
||||
} else {
|
||||
StartDMResult.DmDoesNotExist
|
||||
}
|
||||
},
|
||||
onFailure = { error ->
|
||||
StartDMResult.Failure(error)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface StartDMResult {
|
||||
data class Success(val roomId: RoomId, val isNew: Boolean) : StartDMResult
|
||||
data object DmDoesNotExist : StartDMResult
|
||||
data class Failure(val throwable: Throwable) : StartDMResult
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room
|
||||
|
||||
enum class StateEventType {
|
||||
POLICY_RULE_ROOM,
|
||||
POLICY_RULE_SERVER,
|
||||
POLICY_RULE_USER,
|
||||
CALL_MEMBER,
|
||||
ROOM_ALIASES,
|
||||
ROOM_AVATAR,
|
||||
ROOM_CANONICAL_ALIAS,
|
||||
ROOM_CREATE,
|
||||
ROOM_ENCRYPTION,
|
||||
ROOM_GUEST_ACCESS,
|
||||
ROOM_HISTORY_VISIBILITY,
|
||||
ROOM_JOIN_RULES,
|
||||
ROOM_MEMBER_EVENT,
|
||||
ROOM_NAME,
|
||||
ROOM_PINNED_EVENTS,
|
||||
ROOM_POWER_LEVELS,
|
||||
ROOM_SERVER_ACL,
|
||||
ROOM_THIRD_PARTY_INVITE,
|
||||
ROOM_TOMBSTONE,
|
||||
ROOM_TOPIC,
|
||||
SPACE_CHILD,
|
||||
SPACE_PARENT
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room.alias
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
|
||||
/**
|
||||
* Return true if the given roomIdOrAlias is the same room as this room.
|
||||
*/
|
||||
fun BaseRoom.matches(roomIdOrAlias: RoomIdOrAlias): Boolean {
|
||||
return when (roomIdOrAlias) {
|
||||
is RoomIdOrAlias.Id -> {
|
||||
roomIdOrAlias.roomId == roomId
|
||||
}
|
||||
is RoomIdOrAlias.Alias -> {
|
||||
roomIdOrAlias.roomAlias == info().canonicalAlias || roomIdOrAlias.roomAlias in info().alternativeAliases
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.room.alias
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
/**
|
||||
* Information about a room, that was resolved from a room alias.
|
||||
*/
|
||||
data class ResolvedRoomAlias(
|
||||
/**
|
||||
* The room ID that the alias resolved to.
|
||||
*/
|
||||
val roomId: RoomId,
|
||||
/**
|
||||
* A list of servers that can be used to find the room by its room ID.
|
||||
*/
|
||||
val servers: List<String>
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user