mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 21:32:34 +00:00
Merge branch 'develop' into fix/5794-account-data-api
This commit is contained in:
+51
-32
@@ -49,7 +49,6 @@ import io.element.android.libraries.matrix.api.scanner.ContentScanner
|
||||
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.SyncState
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
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.user.UserStatus
|
||||
@@ -58,7 +57,6 @@ import io.element.android.libraries.matrix.impl.exception.mapClientException
|
||||
import io.element.android.libraries.matrix.impl.linknewdevice.RustLinkDesktopHandler
|
||||
import io.element.android.libraries.matrix.impl.linknewdevice.RustLinkMobileHandler
|
||||
import io.element.android.libraries.matrix.impl.linknewdevice.RustQrCodeDataParser
|
||||
import io.element.android.libraries.matrix.impl.mapper.map
|
||||
import io.element.android.libraries.matrix.impl.media.RustMediaLoader
|
||||
import io.element.android.libraries.matrix.impl.media.RustMediaPreviewService
|
||||
import io.element.android.libraries.matrix.impl.notification.RustNotificationService
|
||||
@@ -85,7 +83,9 @@ import io.element.android.libraries.matrix.impl.search.RustMessageSearchService
|
||||
import io.element.android.libraries.matrix.impl.spaces.RustSpaceService
|
||||
import io.element.android.libraries.matrix.impl.sync.RustSyncService
|
||||
import io.element.android.libraries.matrix.impl.sync.map
|
||||
import io.element.android.libraries.matrix.impl.usersearch.UserSearchResultMapper
|
||||
import io.element.android.libraries.matrix.impl.user.UserSearchResultMapper
|
||||
import io.element.android.libraries.matrix.impl.user.into
|
||||
import io.element.android.libraries.matrix.impl.user.map
|
||||
import io.element.android.libraries.matrix.impl.util.cancelAndDestroy
|
||||
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
|
||||
import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService
|
||||
@@ -126,9 +126,11 @@ import org.matrix.rustcomponents.sdk.IgnoredUsersListener
|
||||
import org.matrix.rustcomponents.sdk.Membership
|
||||
import org.matrix.rustcomponents.sdk.NotificationProcessSetup
|
||||
import org.matrix.rustcomponents.sdk.PowerLevels
|
||||
import org.matrix.rustcomponents.sdk.ProfileListener
|
||||
import org.matrix.rustcomponents.sdk.RoomInfoListener
|
||||
import org.matrix.rustcomponents.sdk.SendQueueRoomErrorListener
|
||||
import org.matrix.rustcomponents.sdk.TaskHandle
|
||||
import org.matrix.rustcomponents.sdk.UserProfile
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
@@ -271,8 +273,6 @@ class RustMatrixClient(
|
||||
|
||||
private var clientDelegateTaskHandle: TaskHandle? = innerClient.setDelegate(sessionDelegate)
|
||||
|
||||
@Volatile private var localUserStatus: UserStatus? = null
|
||||
|
||||
private val _userProfile: MutableStateFlow<MatrixUser> = MutableStateFlow(
|
||||
MatrixUser(
|
||||
userId = sessionId,
|
||||
@@ -283,6 +283,14 @@ class RustMatrixClient(
|
||||
|
||||
override val userProfile: StateFlow<MatrixUser> = _userProfile
|
||||
|
||||
private var ownProfileTaskHandle: TaskHandle? = null
|
||||
|
||||
private val ownProfileListener = object : ProfileListener {
|
||||
override fun onUpdate(profile: UserProfile) {
|
||||
_userProfile.tryEmit(profile.map())
|
||||
}
|
||||
}
|
||||
|
||||
override val ignoredUsersFlow = mxCallbackFlow<ImmutableList<UserId>> {
|
||||
// Fetch the initial value manually, the SDK won't return it automatically
|
||||
channel.trySend(innerClient.ignoredUsers().map(::UserId).toImmutableList())
|
||||
@@ -303,8 +311,21 @@ class RustMatrixClient(
|
||||
sessionCoroutineScope.launch {
|
||||
// Start notification settings
|
||||
notificationSettingsService.start()
|
||||
// Setup user profile logic
|
||||
setupUserProfile()
|
||||
}
|
||||
|
||||
// Update the user profile in the session store if needed
|
||||
// Schedule regular database vacuuming to ensure DB performance remains optimal
|
||||
scheduleDatabaseVacuum()
|
||||
}
|
||||
|
||||
private suspend fun setupUserProfile() {
|
||||
val supported = isUserStatusSupported().getOrDefault(false)
|
||||
if (supported) {
|
||||
// No need to seed the data here, it's already stored by the sdk.
|
||||
ownProfileTaskHandle = innerClient.subscribeToOwnProfile(ownProfileListener)
|
||||
} else {
|
||||
// Seed from the session store so cold-start UI has something to render before the network fetch.
|
||||
sessionStore.getSession(sessionId.value)?.let { sessionData ->
|
||||
_userProfile.emit(
|
||||
MatrixUser(
|
||||
@@ -314,12 +335,8 @@ class RustMatrixClient(
|
||||
)
|
||||
)
|
||||
}
|
||||
// Force a refresh of the profile
|
||||
getUserProfile()
|
||||
}
|
||||
|
||||
// Schedule regular database vacuuming to ensure DB performance remains optimal
|
||||
scheduleDatabaseVacuum()
|
||||
}
|
||||
|
||||
override fun userIdServerName(): String {
|
||||
@@ -458,13 +475,8 @@ class RustMatrixClient(
|
||||
|
||||
override suspend fun getUserProfile(): Result<MatrixUser> = getProfile(sessionId)
|
||||
.onSuccess { matrixUser ->
|
||||
_userProfile.emit(
|
||||
matrixUser.copy(
|
||||
rawStatus = localUserStatus,
|
||||
displayedStatus = localUserStatus?.let { DisplayedStatus.UserSet(it) },
|
||||
)
|
||||
)
|
||||
// Also update our session storage
|
||||
_userProfile.emit(matrixUser)
|
||||
sessionStore.updateUserProfile(
|
||||
sessionId = sessionId.value,
|
||||
displayName = matrixUser.displayName,
|
||||
@@ -495,25 +507,31 @@ class RustMatrixClient(
|
||||
}
|
||||
|
||||
override suspend fun setUserStatus(status: UserStatus): Result<Unit> = withContext(sessionDispatcher) {
|
||||
localUserStatus = status
|
||||
_userProfile.emit(
|
||||
_userProfile.value.copy(
|
||||
rawStatus = status,
|
||||
displayedStatus = DisplayedStatus.UserSet(status),
|
||||
)
|
||||
)
|
||||
Result.success(Unit)
|
||||
runCatchingExceptions {
|
||||
innerClient.setUserStatus(status.into())
|
||||
}.onSuccess {
|
||||
// The subscription pushes the update on supported servers; only refresh manually as a fallback.
|
||||
if (ownProfileTaskHandle == null) getUserProfile()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clearUserStatus(): Result<Unit> = withContext(sessionDispatcher) {
|
||||
localUserStatus = null
|
||||
_userProfile.emit(
|
||||
_userProfile.value.copy(
|
||||
rawStatus = null,
|
||||
displayedStatus = null,
|
||||
)
|
||||
)
|
||||
Result.success(Unit)
|
||||
runCatchingExceptions {
|
||||
innerClient.clearUserStatus()
|
||||
}.onSuccess {
|
||||
// The subscription pushes the update on supported servers; only refresh manually as a fallback.
|
||||
if (ownProfileTaskHandle == null) getUserProfile()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun isUserStatusSupported(): Result<Boolean> = withContext(sessionDispatcher) {
|
||||
runCatchingExceptions {
|
||||
innerClient.isUserStatusSupported()
|
||||
}
|
||||
}
|
||||
|
||||
override fun enableAutomaticCallStatus(enabled: Boolean) {
|
||||
innerClient.enableAutomaticCallStatus(enabled)
|
||||
}
|
||||
|
||||
override suspend fun joinRoom(roomId: RoomId): Result<RoomInfo?> = withContext(sessionDispatcher) {
|
||||
@@ -621,6 +639,7 @@ class RustMatrixClient(
|
||||
|
||||
sessionCoroutineScope.cancel()
|
||||
clientDelegateTaskHandle?.cancelAndDestroy()
|
||||
ownProfileTaskHandle?.cancelAndDestroy()
|
||||
sessionVerificationService.destroy()
|
||||
|
||||
sessionDelegate.clearCurrentClient()
|
||||
|
||||
+13
-14
@@ -17,7 +17,7 @@ import io.element.android.libraries.di.CacheDirectory
|
||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.paths.SessionPaths
|
||||
import io.element.android.libraries.matrix.api.scanner.ContentScannerUrlProvider
|
||||
import io.element.android.libraries.matrix.impl.analytics.UtdTracker
|
||||
@@ -70,7 +70,7 @@ class RustMatrixClientFactory(
|
||||
private val clientBuilderProvider: ClientBuilderProvider,
|
||||
private val sqliteStoreBuilderProvider: SqliteStoreBuilderProvider,
|
||||
private val workManagerScheduler: WorkManagerScheduler,
|
||||
private val contentScannerUrlProvider: ContentScannerUrlProvider,
|
||||
private val contentScannerUrlProviderFactory: ContentScannerUrlProvider.Factory,
|
||||
) {
|
||||
private val sessionDelegate = RustClientSessionDelegate(
|
||||
sessionStore = sessionStore,
|
||||
@@ -135,26 +135,25 @@ class RustMatrixClientFactory(
|
||||
|
||||
client.setUtdDelegate(UtdTracker(analyticsService))
|
||||
|
||||
val domainName = UserId(client.userId()).domainName
|
||||
// If a content scanner URL is available for the homeserver, create a RustContentScanner and set it on the client.
|
||||
// This allows the SDK to use the content scanner for automatic media scanning.
|
||||
// If no content scanner URL is available, the contentScanner will be null.
|
||||
val contentScanner = domainName?.let {
|
||||
contentScannerUrlProvider.getContentScannerUrl(domainName)
|
||||
.getOrNull()
|
||||
?.let { contentScannerUrl ->
|
||||
val contentScanner = ContentScanner(contentScannerUrl)
|
||||
client.setContentScanner(contentScanner)
|
||||
RustContentScanner(
|
||||
client = client,
|
||||
rustScanner = contentScanner,
|
||||
)
|
||||
}
|
||||
val contentScannerUrlProvider = contentScannerUrlProviderFactory.create(RustTemporaryMatrixClient(client, null))
|
||||
val contentScanner = contentScannerUrlProvider.getContentScannerUrl(SessionId(client.userId()))
|
||||
.getOrNull()
|
||||
?.let { contentScannerUrl ->
|
||||
val contentScanner = ContentScanner(contentScannerUrl)
|
||||
client.setContentScanner(contentScanner)
|
||||
RustContentScanner(
|
||||
client = client,
|
||||
rustScanner = contentScanner,
|
||||
)
|
||||
}
|
||||
|
||||
val syncService = client.syncService()
|
||||
.withSharePos(true)
|
||||
.withOfflineMode()
|
||||
.withProfilesExtension()
|
||||
.finish()
|
||||
|
||||
return RustMatrixClient(
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations 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.impl
|
||||
|
||||
import io.element.android.libraries.core.extensions.mapFailure
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.matrix.api.TemporaryMatrixClient
|
||||
import io.element.android.libraries.matrix.api.paths.SessionPaths
|
||||
import io.element.android.libraries.matrix.impl.exception.mapClientException
|
||||
import org.matrix.rustcomponents.sdk.Client
|
||||
|
||||
class RustTemporaryMatrixClient(
|
||||
private val client: Client,
|
||||
private val paths: SessionPaths?,
|
||||
) : TemporaryMatrixClient {
|
||||
override suspend fun getUrl(url: String): Result<ByteArray> = runCatchingExceptions {
|
||||
client.getUrl(url)
|
||||
}.mapFailure { it.mapClientException() }
|
||||
|
||||
override fun close() {
|
||||
client.close()
|
||||
paths?.let {
|
||||
if (it.fileDirectory.exists()) {
|
||||
it.fileDirectory.deleteRecursively()
|
||||
}
|
||||
if (it.cacheDirectory.exists()) {
|
||||
it.cacheDirectory.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations 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.impl
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.matrix.api.TemporaryMatrixClient
|
||||
import io.element.android.libraries.matrix.api.TemporaryMatrixClientFactory
|
||||
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class RustTemporaryMatrixClientFactory(
|
||||
private val sessionPathsFactory: SessionPathsFactory,
|
||||
private val rustMatrixClientFactory: RustMatrixClientFactory,
|
||||
) : TemporaryMatrixClientFactory {
|
||||
override suspend fun create(homeServerUrl: String): Result<TemporaryMatrixClient> {
|
||||
return runCatchingExceptions {
|
||||
val sessionPaths = sessionPathsFactory.create()
|
||||
val client = rustMatrixClientFactory.getBaseClientBuilder(
|
||||
sessionPaths = sessionPaths,
|
||||
clientSecret = null,
|
||||
slidingSyncType = ClientBuilderSlidingSync.Native,
|
||||
isMessageSearchAvailable = false,
|
||||
)
|
||||
.homeserverUrl(homeServerUrl)
|
||||
.build()
|
||||
RustTemporaryMatrixClient(client, sessionPaths)
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-7
@@ -35,6 +35,7 @@ import io.element.android.libraries.matrix.api.paths.SessionPaths
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||
import io.element.android.libraries.matrix.impl.ClientBuilderSlidingSync
|
||||
import io.element.android.libraries.matrix.impl.RustMatrixClientFactory
|
||||
import io.element.android.libraries.matrix.impl.RustTemporaryMatrixClient
|
||||
import io.element.android.libraries.matrix.impl.auth.qrlogin.QrErrorMapper
|
||||
import io.element.android.libraries.matrix.impl.auth.qrlogin.SdkQrCodeLoginData
|
||||
import io.element.android.libraries.matrix.impl.auth.qrlogin.toStep
|
||||
@@ -133,9 +134,10 @@ class RustMatrixAuthenticationService(
|
||||
}
|
||||
|
||||
currentClient = client
|
||||
|
||||
client.homeserverLoginDetails().map()
|
||||
}.onFailure {
|
||||
clear()
|
||||
clear(destroyClient = true)
|
||||
}.mapFailure { failure ->
|
||||
Timber.e(failure, "Failed to set homeserver to $homeserver")
|
||||
failure.mapAuthenticationException()
|
||||
@@ -168,7 +170,7 @@ class RustMatrixAuthenticationService(
|
||||
sessionStore.addSession(sessionData)
|
||||
|
||||
// Clean up the strong reference held here since it's no longer necessary
|
||||
currentClient = null
|
||||
clear(destroyClient = false)
|
||||
|
||||
SessionId(sessionData.userId)
|
||||
}.mapFailure { failure ->
|
||||
@@ -248,7 +250,7 @@ class RustMatrixAuthenticationService(
|
||||
sessionStore.addSession(sessionData)
|
||||
|
||||
// Clean up the strong reference held here since it's no longer necessary
|
||||
currentClient = null
|
||||
clear(destroyClient = false)
|
||||
|
||||
SessionId(sessionData.userId)
|
||||
}
|
||||
@@ -271,11 +273,13 @@ class RustMatrixAuthenticationService(
|
||||
deviceId = null,
|
||||
additionalScopes = emptyList(),
|
||||
)
|
||||
val getUrlResolver = RustTemporaryMatrixClient(client, sessionPaths)
|
||||
val url = oAuthAuthorizationData.loginUrl()
|
||||
.let {
|
||||
enterpriseService.tweakMasUrl(
|
||||
url = it,
|
||||
homeserver = client.server() ?: client.homeserver(),
|
||||
urlContentFetcher = getUrlResolver,
|
||||
)
|
||||
}
|
||||
pendingOAuthAuthorizationData = oAuthAuthorizationData
|
||||
@@ -335,7 +339,7 @@ class RustMatrixAuthenticationService(
|
||||
sessionStore.addSession(sessionData)
|
||||
|
||||
// Clean up the strong reference held here since it's no longer necessary
|
||||
currentClient = null
|
||||
clear(destroyClient = false)
|
||||
|
||||
SessionId(sessionData.userId)
|
||||
}.mapFailure { failure ->
|
||||
@@ -398,7 +402,7 @@ class RustMatrixAuthenticationService(
|
||||
sessionStore.addSession(sessionData)
|
||||
|
||||
// Clean up the strong reference held here since it's no longer necessary
|
||||
currentClient = null
|
||||
clear(destroyClient = false)
|
||||
|
||||
SessionId(sessionData.userId)
|
||||
}.mapFailure {
|
||||
@@ -464,8 +468,10 @@ class RustMatrixAuthenticationService(
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun clear() {
|
||||
currentClient?.close()
|
||||
private fun clear(destroyClient: Boolean) {
|
||||
if (destroyClient) {
|
||||
currentClient?.close()
|
||||
}
|
||||
currentClient = null
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -15,6 +15,7 @@ import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.HomeserverCapabilitiesProvider
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.UrlContentFetcher
|
||||
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
@@ -108,4 +109,9 @@ object SessionMatrixModule {
|
||||
fun providesContentScanner(matrixClient: MatrixClient): ContentScanner? {
|
||||
return matrixClient.contentScanner
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun providesGetUrlResolverClient(matrixClient: MatrixClient): UrlContentFetcher {
|
||||
return matrixClient
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -53,6 +53,7 @@ import io.element.android.libraries.matrix.impl.room.threads.RustThreadsListServ
|
||||
import io.element.android.libraries.matrix.impl.roomdirectory.map
|
||||
import io.element.android.libraries.matrix.impl.timeline.RustTimeline
|
||||
import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper
|
||||
import io.element.android.libraries.matrix.impl.user.map
|
||||
import io.element.android.libraries.matrix.impl.util.MessageEventContent
|
||||
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
|
||||
import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver
|
||||
|
||||
+6
-1
@@ -16,12 +16,15 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.impl.room.history.map
|
||||
import io.element.android.libraries.matrix.impl.room.join.map
|
||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
|
||||
import io.element.android.libraries.matrix.impl.room.powerlevels.RoomPowerLevelsValuesMapper
|
||||
import io.element.android.libraries.matrix.impl.room.tombstone.map
|
||||
import io.element.android.libraries.matrix.impl.user.from
|
||||
import io.element.android.libraries.matrix.impl.user.into
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import org.matrix.rustcomponents.sdk.Membership
|
||||
@@ -102,7 +105,9 @@ fun RustRoomNotificationMode.map(): RoomNotificationMode = when (this) {
|
||||
fun RoomHero.map(): MatrixUser = MatrixUser(
|
||||
userId = UserId(userId),
|
||||
displayName = displayName,
|
||||
avatarUrl = avatarUrl
|
||||
avatarUrl = avatarUrl,
|
||||
displayedStatus = DisplayedStatus.from(status, call),
|
||||
rawStatus = status?.into(),
|
||||
)
|
||||
|
||||
fun mapPowerLevels(roomPowerLevels: RustRoomPowerLevels): RoomPowerLevels {
|
||||
|
||||
+3
-2
@@ -11,7 +11,9 @@ package io.element.android.libraries.matrix.impl.room.member
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
import io.element.android.libraries.matrix.impl.room.powerlevels.into
|
||||
import io.element.android.libraries.matrix.impl.user.from
|
||||
import uniffi.matrix_sdk.RoomMemberRole
|
||||
import org.matrix.rustcomponents.sdk.MembershipState as RustMembershipState
|
||||
import org.matrix.rustcomponents.sdk.RoomMember as RustRoomMember
|
||||
@@ -30,8 +32,7 @@ object RoomMemberMapper {
|
||||
role = mapRole(roomMember.suggestedRoleForPowerLevel, powerLevel),
|
||||
membershipChangeReason = roomMember.membershipChangeReason,
|
||||
isServiceMember = roomMember.isServiceMember,
|
||||
// TODO map this value when available
|
||||
displayedStatus = null,
|
||||
displayedStatus = DisplayedStatus.from(roomMember.status, roomMember.call),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations 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.impl.scanner
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.matrix.api.scanner.ContentScannerUrlProvider
|
||||
|
||||
/**
|
||||
* Default FOSS implementation of [ContentScannerUrlProvider] that returns `null` for the content scanner URL.
|
||||
*/
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultContentScannerUrlProvider : ContentScannerUrlProvider {
|
||||
override suspend fun getContentScannerUrl(homeserver: String): Result<String?> = Result.success(null)
|
||||
}
|
||||
+3
-2
@@ -21,7 +21,9 @@ import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetail
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSender
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.Receipt
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
import io.element.android.libraries.matrix.impl.core.RustSendHandle
|
||||
import io.element.android.libraries.matrix.impl.user.from
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -75,8 +77,7 @@ fun RustProfileDetails.map(): ProfileDetails {
|
||||
displayName = displayName,
|
||||
displayNameAmbiguous = displayNameAmbiguous,
|
||||
avatarUrl = avatarUrl,
|
||||
// TODO map this value when available
|
||||
displayedStatus = null,
|
||||
displayedStatus = DisplayedStatus.from(status, call),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -6,9 +6,10 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.mapper
|
||||
package io.element.android.libraries.matrix.impl.user
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import org.matrix.rustcomponents.sdk.UserProfile
|
||||
|
||||
@@ -16,4 +17,6 @@ fun UserProfile.map() = MatrixUser(
|
||||
userId = UserId(userId),
|
||||
displayName = displayName,
|
||||
avatarUrl = avatarUrl,
|
||||
rawStatus = status?.into(),
|
||||
displayedStatus = DisplayedStatus.from(status, call)
|
||||
)
|
||||
+1
-2
@@ -6,10 +6,9 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.impl.usersearch
|
||||
package io.element.android.libraries.matrix.impl.user
|
||||
|
||||
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
||||
import io.element.android.libraries.matrix.impl.mapper.map
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.matrix.rustcomponents.sdk.SearchUsersResults
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations 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.impl.user
|
||||
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
import io.element.android.libraries.matrix.api.user.UserStatus
|
||||
import org.matrix.rustcomponents.sdk.UserCall
|
||||
import org.matrix.rustcomponents.sdk.UserStatus as RustUserStatus
|
||||
|
||||
fun UserStatus.into(): RustUserStatus {
|
||||
return RustUserStatus(emoji = emoji, text = text)
|
||||
}
|
||||
|
||||
fun RustUserStatus.into(): UserStatus {
|
||||
return UserStatus(emoji = emoji, text = text)
|
||||
}
|
||||
|
||||
fun DisplayedStatus.Companion.from(userStatus: RustUserStatus?, userCall: UserCall?): DisplayedStatus? {
|
||||
return when {
|
||||
userStatus != null -> DisplayedStatus.UserSet(userStatus.into())
|
||||
userCall != null -> DisplayedStatus.InCall(userCall.callJoinedTs?.toLong())
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -13,7 +13,7 @@ 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.verification.SessionVerificationRequestDetails
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
||||
import io.element.android.libraries.matrix.impl.mapper.map
|
||||
import io.element.android.libraries.matrix.impl.user.map
|
||||
import org.matrix.rustcomponents.sdk.SessionVerificationRequestDetails as RustSessionVerificationRequestDetails
|
||||
|
||||
fun RustSessionVerificationRequestDetails.map() = SessionVerificationRequestDetails(
|
||||
|
||||
+2
-2
@@ -52,7 +52,7 @@ fun TestScope.createRustMatrixClientFactory(
|
||||
),
|
||||
clientBuilderProvider: ClientBuilderProvider = FakeClientBuilderProvider(),
|
||||
workManagerScheduler: FakeWorkManagerScheduler = FakeWorkManagerScheduler(),
|
||||
contentScannerUrlProvider: ContentScannerUrlProvider = { Result.success(null) },
|
||||
contentScannerUrlProviderFactory: ContentScannerUrlProvider.Factory = { ContentScannerUrlProvider { Result.success(null) } },
|
||||
) = RustMatrixClientFactory(
|
||||
cacheDirectory = cacheDirectory,
|
||||
appCoroutineScope = backgroundScope,
|
||||
@@ -67,5 +67,5 @@ fun TestScope.createRustMatrixClientFactory(
|
||||
clientBuilderProvider = clientBuilderProvider,
|
||||
sqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(),
|
||||
workManagerScheduler = workManagerScheduler,
|
||||
contentScannerUrlProvider = contentScannerUrlProvider,
|
||||
contentScannerUrlProviderFactory = contentScannerUrlProviderFactory,
|
||||
)
|
||||
|
||||
+4
-3
@@ -32,9 +32,9 @@ import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.matrix.rustcomponents.sdk.Client
|
||||
@@ -75,7 +75,8 @@ class RustMatrixClientTest {
|
||||
|
||||
@Test
|
||||
fun `retrieving the UserProfile updates the database`() = runTest {
|
||||
val updateUserProfileResult = lambdaRecorder<String, String?, String?, Unit> { _, _, _ -> }
|
||||
val profilePersisted = CompletableDeferred<Unit>()
|
||||
val updateUserProfileResult = lambdaRecorder<String, String?, String?, Unit> { _, _, _ -> profilePersisted.complete(Unit) }
|
||||
val sessionStore = InMemorySessionStore(
|
||||
initialList = listOf(
|
||||
aSessionData(
|
||||
@@ -100,7 +101,7 @@ class RustMatrixClientTest {
|
||||
),
|
||||
sessionStore = sessionStore,
|
||||
)
|
||||
advanceUntilIdle()
|
||||
profilePersisted.await()
|
||||
updateUserProfileResult.assertions().isCalledOnce()
|
||||
.with(
|
||||
value(A_USER_ID.value),
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations 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.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.scanner.ContentScannerUrlProvider
|
||||
import io.element.android.libraries.matrix.impl.auth.FakeProxyProvider
|
||||
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
|
||||
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventFilterFactory
|
||||
import io.element.android.libraries.matrix.impl.storage.FakeSqliteStoreBuilderProvider
|
||||
import io.element.android.libraries.network.useragent.SimpleUserAgentProvider
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
class RustTemporaryMatrixClientFactoryTest {
|
||||
@Test
|
||||
fun `create returns a TemporaryMatrixClient`() = runTest {
|
||||
val sut = createRustTemporaryMatrixClientFactory()
|
||||
val result = sut.create("https://matrix.org")
|
||||
assertThat(result.isSuccess).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create can fail gracefully`() = runTest {
|
||||
val sut = createRustTemporaryMatrixClientFactory(
|
||||
rustMatrixClientFactory = createRustMatrixClientFactory(
|
||||
clientBuilderProvider = FakeClientBuilderProvider(
|
||||
provideResult = { throw IllegalStateException("Failed to create client builder") }
|
||||
)
|
||||
)
|
||||
)
|
||||
val result = sut.create("https://matrix.org")
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
private fun TestScope.createRustTemporaryMatrixClientFactory(
|
||||
sessionPathsFactory: SessionPathsFactory = SessionPathsFactory(File("/base"), File("/cache")),
|
||||
rustMatrixClientFactory: RustMatrixClientFactory = createRustMatrixClientFactory(),
|
||||
): RustTemporaryMatrixClientFactory {
|
||||
return RustTemporaryMatrixClientFactory(
|
||||
sessionPathsFactory = sessionPathsFactory,
|
||||
rustMatrixClientFactory = rustMatrixClientFactory,
|
||||
)
|
||||
}
|
||||
|
||||
private fun TestScope.createRustMatrixClientFactory(
|
||||
cacheDirectory: File = File("/cache"),
|
||||
sessionStore: SessionStore = InMemorySessionStore(
|
||||
updateUserProfileResult = { _, _, _ -> },
|
||||
),
|
||||
clientBuilderProvider: ClientBuilderProvider = FakeClientBuilderProvider(),
|
||||
workManagerScheduler: FakeWorkManagerScheduler = FakeWorkManagerScheduler(),
|
||||
contentScannerUrlProviderFactory: ContentScannerUrlProvider.Factory = { ContentScannerUrlProvider { Result.success(null) } },
|
||||
) = RustMatrixClientFactory(
|
||||
cacheDirectory = cacheDirectory,
|
||||
appCoroutineScope = backgroundScope,
|
||||
coroutineDispatchers = testCoroutineDispatchers(),
|
||||
sessionStore = sessionStore,
|
||||
userAgentProvider = SimpleUserAgentProvider(),
|
||||
proxyProvider = FakeProxyProvider(),
|
||||
clock = FakeSystemClock(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
featureFlagService = FakeFeatureFlagService(),
|
||||
timelineEventFilterFactory = FakeTimelineEventFilterFactory(),
|
||||
clientBuilderProvider = clientBuilderProvider,
|
||||
sqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(),
|
||||
workManagerScheduler = workManagerScheduler,
|
||||
contentScannerUrlProviderFactory = contentScannerUrlProviderFactory,
|
||||
)
|
||||
}
|
||||
+24
@@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.test.auth.FakeOAuthRedirectUrlProvide
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -50,6 +51,29 @@ class RustMatrixAuthenticationServiceTest {
|
||||
assertThat(sut.setHomeserver("matrix.org").isSuccess).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setHomeserver can fail gracefully and clean up the temporary client`() = runTest {
|
||||
val closeResult = lambdaRecorder<Unit> {}
|
||||
val sut = createRustMatrixAuthenticationService(
|
||||
clientBuilderProvider = FakeClientBuilderProvider(
|
||||
provideResult = {
|
||||
FakeFfiClientBuilder(
|
||||
buildResult = {
|
||||
FakeFfiClient(
|
||||
homeserverLoginDetailsResult = {
|
||||
throw IllegalStateException("Failed to get homeserver login details")
|
||||
},
|
||||
closeResult = closeResult,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
assertThat(sut.setHomeserver("matrix.org").isFailure).isTrue()
|
||||
closeResult.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
private fun TestScope.createRustMatrixAuthenticationService(
|
||||
sessionStore: SessionStore = InMemorySessionStore(),
|
||||
clientBuilderProvider: ClientBuilderProvider = FakeClientBuilderProvider(),
|
||||
|
||||
+10
@@ -26,6 +26,7 @@ import org.matrix.rustcomponents.sdk.NoHandle
|
||||
import org.matrix.rustcomponents.sdk.NotificationClient
|
||||
import org.matrix.rustcomponents.sdk.NotificationProcessSetup
|
||||
import org.matrix.rustcomponents.sdk.NotificationSettings
|
||||
import org.matrix.rustcomponents.sdk.ProfileListener
|
||||
import org.matrix.rustcomponents.sdk.PusherIdentifiers
|
||||
import org.matrix.rustcomponents.sdk.PusherKind
|
||||
import org.matrix.rustcomponents.sdk.RoomDirectorySearch
|
||||
@@ -57,6 +58,8 @@ class FakeFfiClient(
|
||||
private val homeserverCapabilities: HomeserverCapabilities = FakeFfiHomeserverCapabilities(),
|
||||
private val accountDataResult: (String) -> String? = { lambdaError() },
|
||||
private val setAccountDataResult: (String, String) -> Unit = { _, _ -> lambdaError() },
|
||||
private val isUserStatusSupportedResult: () -> Boolean = { false },
|
||||
private val subscribeToOwnProfileResult: (ProfileListener) -> Unit = {},
|
||||
private val closeResult: () -> Unit = {},
|
||||
) : Client(NoHandle) {
|
||||
override fun userId(): String = userId
|
||||
@@ -94,6 +97,13 @@ class FakeFfiClient(
|
||||
return FakeFfiTaskHandle()
|
||||
}
|
||||
|
||||
override suspend fun isUserStatusSupported(): Boolean = isUserStatusSupportedResult()
|
||||
|
||||
override fun subscribeToOwnProfile(listener: ProfileListener): TaskHandle {
|
||||
subscribeToOwnProfileResult(listener)
|
||||
return FakeFfiTaskHandle()
|
||||
}
|
||||
|
||||
override suspend fun getProfile(userId: String): UserProfile {
|
||||
return getProfileResult(userId)
|
||||
}
|
||||
|
||||
+1
@@ -15,5 +15,6 @@ import org.matrix.rustcomponents.sdk.SyncServiceBuilder
|
||||
class FakeFfiSyncServiceBuilder : SyncServiceBuilder(NoHandle) {
|
||||
override fun withOfflineMode(): SyncServiceBuilder = this
|
||||
override fun withSharePos(enable: Boolean): SyncServiceBuilder = this
|
||||
override fun withProfilesExtension(): SyncServiceBuilder = this
|
||||
override suspend fun finish(): SyncService = FakeFfiSyncService()
|
||||
}
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ package io.element.android.libraries.matrix.impl.mapper
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustUserProfile
|
||||
import io.element.android.libraries.matrix.impl.user.map
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import org.junit.Test
|
||||
|
||||
|
||||
+1
@@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustSearchUsersResults
|
||||
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustUserProfile
|
||||
import io.element.android.libraries.matrix.impl.user.UserSearchResultMapper
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.junit.Test
|
||||
|
||||
Reference in New Issue
Block a user