Merge branch 'develop' into separate_import_error

This commit is contained in:
Hubert Chathi 2025-10-02 14:33:55 -04:00 committed by GitHub
commit 8f8e190e68
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2773 changed files with 29051 additions and 10914 deletions

View file

@ -1,6 +1,7 @@
import config.BuildTimeConfig
import extension.buildConfigFieldStr
import extension.setupAnvil
import extension.setupDependencyInjection
import extension.testCommonDependencies
/*
* Copyright 2022-2024 New Vector Ltd.
@ -15,6 +16,8 @@ plugins {
alias(libs.plugins.kotlin.serialization)
}
setupDependencyInjection()
android {
namespace = "io.element.android.libraries.matrix.api"
@ -42,11 +45,8 @@ android {
}
}
setupAnvil()
dependencies {
implementation(projects.libraries.di)
implementation(libs.dagger)
implementation(projects.libraries.androidutils)
implementation(projects.libraries.core)
implementation(projects.services.analytics.api)
@ -55,7 +55,6 @@ dependencies {
implementation(libs.coroutines.core)
api(projects.libraries.architecture)
testImplementation(libs.test.junit)
testImplementation(libs.test.truth)
testCommonDependencies(libs)
testImplementation(projects.libraries.matrix.test)
}

View file

@ -31,6 +31,7 @@ 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.user.MatrixSearchUserResults
@ -47,6 +48,7 @@ interface MatrixClient {
val deviceId: DeviceId
val userProfile: StateFlow<MatrixUser>
val roomListService: RoomListService
val spaceService: SpaceService
val mediaLoader: MatrixMediaLoader
val sessionCoroutineScope: CoroutineScope
val ignoredUsersFlow: StateFlow<ImmutableList<UserId>>
@ -154,11 +156,6 @@ interface MatrixClient {
*/
suspend fun currentSlidingSyncVersion(): Result<SlidingSyncVersion>
/**
* Returns the available sliding sync versions for the current user.
*/
suspend fun availableSlidingSyncVersions(): Result<List<SlidingSyncVersion>>
fun canDeactivateAccount(): Boolean
suspend fun deactivateAccount(password: String, eraseData: Boolean): Result<Unit>
@ -176,6 +173,16 @@ interface MatrixClient {
* 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>
}
/**

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.api.auth
sealed class AuthenticationException(message: String) : Exception(message) {
class AccountAlreadyLoggedIn(userId: String) : AuthenticationException(userId)
class InvalidServerName(message: String) : AuthenticationException(message)
class SlidingSyncVersion(message: String) : AuthenticationException(message)
class Oidc(message: String) : AuthenticationException(message)

View file

@ -13,14 +13,9 @@ 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
import io.element.android.libraries.sessionstorage.api.LoggedInState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
interface MatrixAuthenticationService {
fun loggedInStateFlow(): Flow<LoggedInState>
suspend fun getLatestSessionId(): SessionId?
/**
* Restore a session from a [sessionId].
* Do not restore anything it the access token is not valid anymore.

View file

@ -151,7 +151,7 @@ object MatrixPatterns {
val urlMatch = match.groupValues[1]
when (val permalink = permalinkParser.parse(urlMatch)) {
is PermalinkData.UserLink -> {
add(MatrixPatternResult(MatrixPatternType.USER_ID, permalink.userId.toString(), match.range.first, match.range.last + 1))
add(MatrixPatternResult(MatrixPatternType.USER_ID, permalink.userId.value, match.range.first, match.range.last + 1))
}
is PermalinkData.RoomLink -> {
when (permalink.roomIdOrAlias) {

View file

@ -7,9 +7,10 @@
package io.element.android.libraries.matrix.api.mxc
import javax.inject.Inject
import dev.zacsweers.metro.Inject
class MxcTools @Inject constructor() {
@Inject
class MxcTools {
/**
* Regex to match a Matrix Content (mxc://) URI.
*

View file

@ -49,9 +49,10 @@ sealed interface NotificationContent {
val senderId: UserId,
) : MessageLike
data class CallNotify(
data class RtcNotification(
val senderId: UserId,
val type: CallNotifyType,
val type: RtcNotificationType,
val expirationTimestampMillis: Long
) : MessageLike
data object CallHangup : MessageLike
@ -118,7 +119,7 @@ sealed interface NotificationContent {
) : NotificationContent
}
enum class CallNotifyType {
enum class RtcNotificationType {
RING,
NOTIFY
}

View file

@ -8,6 +8,7 @@
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
@ -15,13 +16,15 @@ import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
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
sealed interface PermalinkData {
@Parcelize
sealed interface PermalinkData : Parcelable {
data class RoomLink(
val roomIdOrAlias: RoomIdOrAlias,
val eventId: EventId? = null,

View file

@ -0,0 +1,23 @@
/*
* 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.recentemojis
import dev.zacsweers.metro.Inject
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.MatrixClient
import kotlinx.coroutines.withContext
@Inject
class AddRecentEmoji(
private val client: MatrixClient,
private val dispatchers: CoroutineDispatchers,
) {
suspend operator fun invoke(emoji: String): Result<Unit> = withContext(dispatchers.io) {
client.addRecentEmoji(emoji)
}
}

View file

@ -0,0 +1,30 @@
/*
* 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.recentemojis
import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.Inject
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient
import kotlinx.coroutines.withContext
fun interface GetRecentEmojis {
suspend operator fun invoke(): Result<List<String>>
}
@ContributesBinding(SessionScope::class)
@Inject
class DefaultGetRecentEmojis(
private val client: MatrixClient,
private val dispatchers: CoroutineDispatchers,
) : GetRecentEmojis {
override suspend operator fun invoke(): Result<List<String>> = withContext(dispatchers.io) {
client.getRecentEmojis()
}
}

View file

@ -18,6 +18,7 @@ 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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import java.io.Closeable
@ -239,6 +240,12 @@ interface BaseRoom : Closeable {
*/
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.
*/

View file

@ -7,28 +7,32 @@
package io.element.android.libraries.matrix.api.room
enum class MessageEventType {
CALL_ANSWER,
CALL_INVITE,
CALL_HANGUP,
CALL_CANDIDATES,
CALL_NOTIFY,
KEY_VERIFICATION_READY,
KEY_VERIFICATION_START,
KEY_VERIFICATION_CANCEL,
KEY_VERIFICATION_ACCEPT,
KEY_VERIFICATION_KEY,
KEY_VERIFICATION_MAC,
KEY_VERIFICATION_DONE,
REACTION,
ROOM_ENCRYPTED,
ROOM_MESSAGE,
ROOM_REDACTION,
STICKER,
POLL_END,
POLL_RESPONSE,
POLL_START,
UNSTABLE_POLL_END,
UNSTABLE_POLL_RESPONSE,
UNSTABLE_POLL_START,
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
}

View file

@ -17,7 +17,6 @@ data class RoomMember(
val membership: RoomMembershipState,
val isNameAmbiguous: Boolean,
val powerLevel: Long,
val normalizedPowerLevel: Long,
val isIgnored: Boolean,
val role: Role,
val membershipChangeReason: String?,

View file

@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.asSharedFlow
class RoomMembershipObserver {
data class RoomMembershipUpdate(
val roomId: RoomId,
val isSpace: Boolean,
val isUserInRoom: Boolean,
val change: MembershipChange,
)
@ -22,12 +23,23 @@ class RoomMembershipObserver {
private val _updates = MutableSharedFlow<RoomMembershipUpdate>(extraBufferCapacity = 10)
val updates = _updates.asSharedFlow()
suspend fun notifyUserLeftRoom(roomId: RoomId, membershipBeforeLeft: CurrentUserMembership) {
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, false, membershipChange))
_updates.emit(
RoomMembershipUpdate(
roomId = roomId,
isSpace = isSpace,
isUserInRoom = false,
change = membershipChange,
)
)
}
}

View file

@ -7,8 +7,10 @@
package io.element.android.libraries.matrix.api.room.join
import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.api.core.RoomId
@Immutable
sealed interface AllowRule {
data class RoomMembership(val roomId: RoomId) : AllowRule
data class Custom(val json: String) : AllowRule

View file

@ -7,12 +7,16 @@
package io.element.android.libraries.matrix.api.room.join
import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList
@Immutable
sealed interface JoinRule {
data object Public : JoinRule
data object Private : JoinRule
data object Knock : JoinRule
data object Invite : JoinRule
data class Restricted(val rules: List<AllowRule>) : JoinRule
data class KnockRestricted(val rules: List<AllowRule>) : JoinRule
data class Restricted(val rules: ImmutableList<AllowRule>) : JoinRule
data class KnockRestricted(val rules: ImmutableList<AllowRule>) : JoinRule
data class Custom(val value: String) : JoinRule
}

View file

@ -0,0 +1,37 @@
/*
* 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.spaces
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.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomType
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.user.MatrixUser
data class SpaceRoom(
val name: String?,
val avatarUrl: String?,
val canonicalAlias: RoomAlias?,
val childrenCount: Int,
val guestCanJoin: Boolean,
val heroes: List<MatrixUser>,
val joinRule: JoinRule?,
val numJoinedMembers: Int,
val roomId: RoomId,
val roomType: RoomType,
val state: CurrentUserMembership?,
val topic: String?,
val worldReadable: Boolean,
/**
* The via parameters of the room.
*/
val via: List<String>,
) {
val isSpace = roomType == RoomType.Space
}

View file

@ -0,0 +1,30 @@
/*
* 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.spaces
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import java.util.Optional
interface SpaceRoomList {
sealed interface PaginationStatus {
data object Loading : PaginationStatus
data class Idle(val hasMoreToLoad: Boolean) : PaginationStatus
}
val roomId: RoomId
val currentSpaceFlow: StateFlow<Optional<SpaceRoom>>
val spaceRoomsFlow: Flow<List<SpaceRoom>>
val paginationStatusFlow: StateFlow<PaginationStatus>
suspend fun paginate(): Result<Unit>
fun destroy()
}

View file

@ -0,0 +1,18 @@
/*
* 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.spaces
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.coroutines.flow.SharedFlow
interface SpaceService {
val spaceRoomsFlow: SharedFlow<List<SpaceRoom>>
suspend fun joinedSpaces(): Result<List<SpaceRoom>>
fun spaceRoomList(id: RoomId): SpaceRoomList
}

View file

@ -151,7 +151,7 @@ interface Timeline : AutoCloseable {
suspend fun redactEvent(eventOrTransactionId: EventOrTransactionId, reason: String?): Result<Unit>
suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Unit>
suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Boolean>
suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit>

View file

@ -14,10 +14,10 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
data class EventThreadInfo(
val threadRootId: ThreadId?,
val threadSummary: ThreadSummary?,
)
sealed interface EventThreadInfo {
data class ThreadRoot(val summary: ThreadSummary) : EventThreadInfo
data class ThreadResponse(val threadRootId: ThreadId) : EventThreadInfo
}
data class ThreadSummary(
val latestEvent: AsyncData<EmbeddedEventInfo>,

View file

@ -24,7 +24,7 @@ data class MessageContent(
val body: String,
val inReplyTo: InReplyTo?,
val isEdited: Boolean,
val threadInfo: EventThreadInfo,
val threadInfo: EventThreadInfo?,
val type: MessageType
) : EventContent

View file

@ -15,5 +15,6 @@ object EventType {
// Call Events
const val CALL_INVITE = "m.call.invite"
const val CALL_NOTIFY = "m.call.notify"
const val RTC_NOTIFICATION = "org.matrix.msc4075.rtc.notification"
}

View file

@ -1,21 +0,0 @@
/*
* Copyright 2023, 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.
*/
package io.element.android.libraries.matrix.api.user
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.SessionId
import javax.inject.Inject
@SingleIn(SessionScope::class)
class CurrentSessionIdHolder @Inject constructor(matrixClient: MatrixClient) {
val current = matrixClient.sessionId
fun isCurrentSession(sessionId: SessionId?): Boolean = current == sessionId
}

View file

@ -10,20 +10,14 @@ package io.element.android.libraries.matrix.api.verification
import android.os.Parcelable
import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.FlowId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.parcelize.Parcelize
@Parcelize
data class SessionVerificationRequestDetails(
val senderProfile: SenderProfile,
val senderProfile: MatrixUser,
val flowId: FlowId,
val deviceId: DeviceId,
val deviceDisplayName: String?,
val firstSeenTimestamp: Long,
) : Parcelable {
@Parcelize
data class SenderProfile(
val userId: UserId,
val displayName: String?,
val avatarUrl: String?,
) : Parcelable
}
) : Parcelable

View file

@ -15,5 +15,6 @@ interface CallWidgetSettingsProvider {
widgetId: String = UUID.randomUUID().toString(),
encrypted: Boolean,
direct: Boolean,
hasActiveCall: Boolean,
): MatrixWidgetSettings
}