Merge pull request #213 from vector-im/feature/fga/hide_rust_sdk

Feature/fga/hide rust sdk
This commit is contained in:
ganfra 2023-03-14 19:59:41 +01:00 committed by GitHub
commit 3f8263f084
85 changed files with 1442 additions and 272 deletions

View file

@ -71,6 +71,7 @@ class RoomFlowNode @AssistedInject constructor(
}, },
onDestroy = { onDestroy = {
Timber.v("OnDestroy") Timber.v("OnDestroy")
inputs.room.close()
plugins<LifecycleCallback>().forEach { it.onFlowReleased(inputs.room) } plugins<LifecycleCallback>().forEach { it.onFlowReleased(inputs.room) }
} }
) )

View file

@ -119,14 +119,15 @@ class RootFlowNode @AssistedInject constructor(
onSuccess(latestKnownUserId) onSuccess(latestKnownUserId)
return return
} }
val matrixClient = authenticationService.restoreSession(UserId(latestKnownUserId.value)) authenticationService.restoreSession(UserId(latestKnownUserId.value))
if (matrixClient == null) { .onSuccess { matrixClient ->
Timber.v("Failed to restore session...") matrixClientsHolder.add(matrixClient)
onFailure() onSuccess(matrixClient.sessionId)
} else { }
matrixClientsHolder.add(matrixClient) .onFailure {
onSuccess(matrixClient.sessionId) Timber.v("Failed to restore session...")
} onFailure()
}
} }
private fun onOpenBugReport() { private fun onOpenBugReport() {

View file

@ -64,10 +64,13 @@ class MatrixClientsHolder @Inject constructor(private val authenticationService:
runBlocking { runBlocking {
userIds.forEach { userId -> userIds.forEach { userId ->
Timber.v("Restore matrix session: $userId") Timber.v("Restore matrix session: $userId")
val matrixClient = authenticationService.restoreSession(userId) authenticationService.restoreSession(userId)
if (matrixClient != null) { .onSuccess { matrixClient ->
add(matrixClient) add(matrixClient)
} }
.onFailure {
Timber.e("Fail to restore session")
}
} }
} }
} }

View file

@ -214,10 +214,10 @@ koverMerged {
name = "Global minimum code coverage." name = "Global minimum code coverage."
target = kotlinx.kover.api.VerificationTarget.ALL target = kotlinx.kover.api.VerificationTarget.ALL
bound { bound {
minValue = 55 minValue = 50
// Setting a max value, so that if coverage is bigger, it means that we have to change minValue. // Setting a max value, so that if coverage is bigger, it means that we have to change minValue.
// For instance if we have minValue = 25 and maxValue = 30, and current code coverage is now 37.32%, update // For instance if we have minValue = 20 and maxValue = 30, and current code coverage is now 31.32%, update
// minValue to 35 and maxValue to 40. // minValue to 25 and maxValue to 35.
maxValue = 60 maxValue = 60
counter = kotlinx.kover.api.CounterType.INSTRUCTION counter = kotlinx.kover.api.CounterType.INSTRUCTION
valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE
@ -243,6 +243,8 @@ koverMerged {
target = kotlinx.kover.api.VerificationTarget.CLASS target = kotlinx.kover.api.VerificationTarget.CLASS
overrideClassFilter { overrideClassFilter {
includes += "*State" includes += "*State"
excludes += "io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*"
excludes += "io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*"
} }
bound { bound {
minValue = 90 minValue = 90

View file

@ -80,9 +80,9 @@ import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextField import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
import io.element.android.libraries.matrix.api.auth.AuthenticationException
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag import io.element.android.libraries.testtags.testTag
import org.matrix.rustcomponents.sdk.AuthenticationException
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalMaterial3Api::class, ExperimentalTextApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalTextApi::class)

View file

@ -17,8 +17,8 @@
package io.element.android.features.login.impl.error package io.element.android.features.login.impl.error
import io.element.android.libraries.matrix.api.auth.AuthErrorCode import io.element.android.libraries.matrix.api.auth.AuthErrorCode
import io.element.android.libraries.matrix.api.auth.AuthenticationException
import io.element.android.libraries.matrix.api.auth.errorCode import io.element.android.libraries.matrix.api.auth.errorCode
import org.matrix.rustcomponents.sdk.AuthenticationException
import io.element.android.libraries.ui.strings.R.string as StringR import io.element.android.libraries.ui.strings.R.string as StringR
fun loginError( fun loginError(

View file

@ -25,7 +25,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import io.element.android.features.login.impl.util.LoginConstants import io.element.android.features.login.impl.util.LoginConstants
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -71,15 +70,14 @@ class LoginRootPresenter @Inject constructor(private val authenticationService:
private fun CoroutineScope.submit(homeserver: String, formState: LoginFormState, loggedInState: MutableState<LoggedInState>) = launch { private fun CoroutineScope.submit(homeserver: String, formState: LoginFormState, loggedInState: MutableState<LoggedInState>) = launch {
loggedInState.value = LoggedInState.LoggingIn loggedInState.value = LoggedInState.LoggingIn
//TODO rework the setHomeserver flow //TODO rework the setHomeserver flow
tryOrNull { authenticationService.setHomeserver(homeserver)
authenticationService.setHomeserver(homeserver) authenticationService.login(formState.login.trim(), formState.password)
} .onSuccess { sessionId ->
try { loggedInState.value = LoggedInState.LoggedIn(sessionId)
val sessionId = authenticationService.login(formState.login.trim(), formState.password.trim()) }
loggedInState.value = LoggedInState.LoggedIn(sessionId) .onFailure { failure ->
} catch (failure: Throwable) { loggedInState.value = LoggedInState.ErrorLoggingIn(failure)
loggedInState.value = LoggedInState.ErrorLoggingIn(failure) }
}
} }
private fun updateFormState(formState: MutableState<LoginFormState>, updateLambda: LoginFormState.() -> LoginFormState) { private fun updateFormState(formState: MutableState<LoginFormState>, updateLambda: LoginFormState.() -> LoginFormState) {

View file

@ -17,9 +17,9 @@
package io.element.android.features.login.impl.error package io.element.android.features.login.impl.error
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.auth.AuthenticationException
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.R
import org.junit.Test import org.junit.Test
import org.matrix.rustcomponents.sdk.AuthenticationException
class ErrorFormatterTests { class ErrorFormatterTests {

View file

@ -24,6 +24,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -81,7 +82,7 @@ internal fun aTimelineItemEvent(
return TimelineItem.Event( return TimelineItem.Event(
id = randomId, id = randomId,
eventId = EventId(randomId), eventId = EventId(randomId),
senderId = "@senderId", senderId = UserId("@senderId"),
senderAvatar = AvatarData("@senderId", "sender"), senderAvatar = AvatarData("@senderId", "sender"),
content = content, content = content,
reactionsState = TimelineItemReactions( reactionsState = TimelineItemReactions(

View file

@ -24,7 +24,7 @@ import androidx.compose.ui.tooling.preview.Preview
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import org.matrix.rustcomponents.sdk.EncryptedMessage import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
@Composable @Composable
fun TimelineItemEncryptedView( fun TimelineItemEncryptedView(
@ -53,7 +53,7 @@ internal fun TimelineItemEncryptedViewDarkPreview() =
private fun ContentToPreview() { private fun ContentToPreview() {
TimelineItemEncryptedView( TimelineItemEncryptedView(
content = TimelineItemEncryptedContent( content = TimelineItemEncryptedContent(
encryptedMessage = EncryptedMessage.Unknown, data = UnableToDecryptContent.Data.Unknown
) )
) )
} }

View file

@ -17,11 +17,20 @@
package io.element.android.features.messages.impl.timeline.factories.event package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
import javax.inject.Inject import javax.inject.Inject
typealias RustTimelineItemContent = org.matrix.rustcomponents.sdk.TimelineItemContent
class TimelineItemContentFactory @Inject constructor( class TimelineItemContentFactory @Inject constructor(
private val messageFactory: TimelineItemContentMessageFactory, private val messageFactory: TimelineItemContentMessageFactory,
private val redactedMessageFactory: TimelineItemContentRedactedFactory, private val redactedMessageFactory: TimelineItemContentRedactedFactory,
@ -34,17 +43,18 @@ class TimelineItemContentFactory @Inject constructor(
private val failedToParseStateFactory: TimelineItemContentFailedToParseStateFactory private val failedToParseStateFactory: TimelineItemContentFailedToParseStateFactory
) { ) {
fun create(itemContent: RustTimelineItemContent): TimelineItemEventContent { fun create(itemContent: EventContent): TimelineItemEventContent {
return when (val kind = itemContent.kind()) { return when (itemContent) {
is TimelineItemContentKind.Message -> messageFactory.create(itemContent.asMessage()) is FailedToParseMessageLikeContent -> failedToParseMessageFactory.create(itemContent)
is TimelineItemContentKind.RedactedMessage -> redactedMessageFactory.create(kind) is FailedToParseStateContent -> failedToParseStateFactory.create(itemContent)
is TimelineItemContentKind.Sticker -> stickerFactory.create(kind) is MessageContent -> messageFactory.create(itemContent)
is TimelineItemContentKind.UnableToDecrypt -> utdFactory.create(kind) is ProfileChangeContent -> profileChangeFactory.create(itemContent)
is TimelineItemContentKind.RoomMembership -> roomMembershipFactory.create(kind) is RedactedContent -> redactedMessageFactory.create(itemContent)
is TimelineItemContentKind.ProfileChange -> profileChangeFactory.create(kind) is RoomMembershipContent -> roomMembershipFactory.create(itemContent)
is TimelineItemContentKind.State -> stateFactory.create(kind) is StateContent -> stateFactory.create(itemContent)
is TimelineItemContentKind.FailedToParseMessageLike -> failedToParseMessageFactory.create(kind) is StickerContent -> stickerFactory.create(itemContent)
is TimelineItemContentKind.FailedToParseState -> failedToParseStateFactory.create(kind) is UnableToDecryptContent -> utdFactory.create(itemContent)
is UnknownContent -> TimelineItemUnknownContent
} }
} }
} }

View file

@ -18,12 +18,12 @@ package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
import javax.inject.Inject import javax.inject.Inject
class TimelineItemContentFailedToParseMessageFactory @Inject constructor() { class TimelineItemContentFailedToParseMessageFactory @Inject constructor() {
fun create(kind: TimelineItemContentKind.FailedToParseMessageLike): TimelineItemEventContent { fun create(failedToParseMessageLike: FailedToParseMessageLikeContent): TimelineItemEventContent {
return TimelineItemUnknownContent return TimelineItemUnknownContent
} }
} }

View file

@ -18,12 +18,12 @@ package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
import javax.inject.Inject import javax.inject.Inject
class TimelineItemContentFailedToParseStateFactory @Inject constructor() { class TimelineItemContentFailedToParseStateFactory @Inject constructor() {
fun create(kind: TimelineItemContentKind.FailedToParseState): TimelineItemEventContent { fun create(failedToParseState: FailedToParseStateContent): TimelineItemEventContent {
return TimelineItemUnknownContent return TimelineItemUnknownContent
} }
} }

View file

@ -24,43 +24,46 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import io.element.android.features.messages.impl.timeline.util.toHtmlDocument import io.element.android.features.messages.impl.timeline.util.toHtmlDocument
import io.element.android.libraries.matrix.api.media.MediaResolver import io.element.android.libraries.matrix.api.media.MediaResolver
import org.matrix.rustcomponents.sdk.Message import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
import org.matrix.rustcomponents.sdk.MessageType import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
import javax.inject.Inject import javax.inject.Inject
class TimelineItemContentMessageFactory @Inject constructor() { class TimelineItemContentMessageFactory @Inject constructor() {
fun create(contentAsMessage: Message?): TimelineItemEventContent { fun create(content: MessageContent): TimelineItemEventContent {
return when (val messageType = contentAsMessage?.msgtype()) { return when (val messageType = content.type) {
is MessageType.Emote -> TimelineItemEmoteContent( is EmoteMessageType -> TimelineItemEmoteContent(
body = messageType.content.body, body = messageType.body,
htmlDocument = messageType.content.formatted?.toHtmlDocument() htmlDocument = messageType.formatted?.toHtmlDocument()
) )
is MessageType.Image -> { is ImageMessageType -> {
val height = messageType.content.info?.height?.toFloat() val height = messageType.info?.height?.toFloat()
val width = messageType.content.info?.width?.toFloat() val width = messageType.info?.width?.toFloat()
val aspectRatio = if (height != null && width != null) { val aspectRatio = if (height != null && width != null) {
width / height width / height
} else { } else {
0.7f 0.7f
} }
TimelineItemImageContent( TimelineItemImageContent(
body = messageType.content.body, body = messageType.body,
imageMeta = MediaResolver.Meta( imageMeta = MediaResolver.Meta(
source = messageType.content.source, url = messageType.url,
kind = MediaResolver.Kind.Content kind = MediaResolver.Kind.Content
), ),
blurhash = messageType.content.info?.blurhash, blurhash = messageType.info?.blurhash,
aspectRatio = aspectRatio aspectRatio = aspectRatio
) )
} }
is MessageType.Notice -> TimelineItemNoticeContent( is NoticeMessageType -> TimelineItemNoticeContent(
body = messageType.content.body, body = messageType.body,
htmlDocument = messageType.content.formatted?.toHtmlDocument() htmlDocument = messageType.formatted?.toHtmlDocument()
) )
is MessageType.Text -> TimelineItemTextContent( is TextMessageType -> TimelineItemTextContent(
body = messageType.content.body, body = messageType.body,
htmlDocument = messageType.content.formatted?.toHtmlDocument() htmlDocument = messageType.formatted?.toHtmlDocument()
) )
else -> TimelineItemUnknownContent else -> TimelineItemUnknownContent
} }

View file

@ -18,12 +18,12 @@ package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
import javax.inject.Inject import javax.inject.Inject
class TimelineItemContentProfileChangeFactory @Inject constructor() { class TimelineItemContentProfileChangeFactory @Inject constructor() {
fun create(kind: TimelineItemContentKind.ProfileChange): TimelineItemEventContent { fun create(content: ProfileChangeContent): TimelineItemEventContent {
return TimelineItemUnknownContent return TimelineItemUnknownContent
} }
} }

View file

@ -18,12 +18,12 @@ package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
import javax.inject.Inject import javax.inject.Inject
class TimelineItemContentRedactedFactory @Inject constructor() { class TimelineItemContentRedactedFactory @Inject constructor() {
fun create(kind: TimelineItemContentKind.RedactedMessage): TimelineItemEventContent { fun create(content: RedactedContent): TimelineItemEventContent {
return TimelineItemRedactedContent return TimelineItemRedactedContent
} }
} }

View file

@ -18,12 +18,12 @@ package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
import javax.inject.Inject import javax.inject.Inject
class TimelineItemContentRoomMembershipFactory @Inject constructor() { class TimelineItemContentRoomMembershipFactory @Inject constructor() {
fun create(kind: TimelineItemContentKind.RoomMembership): TimelineItemEventContent { fun create(content: RoomMembershipContent): TimelineItemEventContent {
return TimelineItemUnknownContent return TimelineItemUnknownContent
} }
} }

View file

@ -18,12 +18,12 @@ package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
import javax.inject.Inject import javax.inject.Inject
class TimelineItemContentStateFactory @Inject constructor() { class TimelineItemContentStateFactory @Inject constructor() {
fun create(kind: TimelineItemContentKind.State): TimelineItemEventContent { fun create(content: StateContent): TimelineItemEventContent {
return TimelineItemUnknownContent return TimelineItemUnknownContent
} }
} }

View file

@ -18,12 +18,12 @@ package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
import javax.inject.Inject import javax.inject.Inject
class TimelineItemContentStickerFactory @Inject constructor() { class TimelineItemContentStickerFactory @Inject constructor() {
fun create(kind: TimelineItemContentKind.Sticker): TimelineItemEventContent { fun create(content: StickerContent): TimelineItemEventContent {
return TimelineItemUnknownContent return TimelineItemUnknownContent
} }
} }

View file

@ -16,14 +16,14 @@
package io.element.android.features.messages.impl.timeline.factories.event package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
import javax.inject.Inject import javax.inject.Inject
class TimelineItemContentUTDFactory @Inject constructor() { class TimelineItemContentUTDFactory @Inject constructor() {
fun create(kind: TimelineItemContentKind.UnableToDecrypt): TimelineItemEventContent { fun create(content: UnableToDecryptContent): TimelineItemEventContent {
return TimelineItemEncryptedContent(kind.msg) return TimelineItemEncryptedContent(content.data)
} }
} }

View file

@ -23,8 +23,8 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItemReac
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import org.matrix.rustcomponents.sdk.ProfileTimelineDetails
import javax.inject.Inject import javax.inject.Inject
class TimelineItemEventFactory @Inject constructor( class TimelineItemEventFactory @Inject constructor(
@ -36,13 +36,13 @@ class TimelineItemEventFactory @Inject constructor(
index: Int, index: Int,
timelineItems: List<MatrixTimelineItem>, timelineItems: List<MatrixTimelineItem>,
): TimelineItem.Event { ): TimelineItem.Event {
val currentSender = currentTimelineItem.event.sender() val currentSender = currentTimelineItem.event.sender
val groupPosition = val groupPosition =
computeGroupPosition(currentTimelineItem, timelineItems, index) computeGroupPosition(currentTimelineItem, timelineItems, index)
val senderDisplayName: String? val senderDisplayName: String?
val senderAvatarUrl: String? val senderAvatarUrl: String?
when (val senderProfile = currentTimelineItem.event.senderProfile()) { when (val senderProfile = currentTimelineItem.event.senderProfile) {
ProfileTimelineDetails.Unavailable, ProfileTimelineDetails.Unavailable,
ProfileTimelineDetails.Pending, ProfileTimelineDetails.Pending,
is ProfileTimelineDetails.Error -> { is ProfileTimelineDetails.Error -> {
@ -56,8 +56,8 @@ class TimelineItemEventFactory @Inject constructor(
} }
val senderAvatarData = AvatarData( val senderAvatarData = AvatarData(
id = currentSender, id = currentSender.value,
name = senderDisplayName ?: currentSender, name = senderDisplayName ?: currentSender.value,
url = senderAvatarUrl, url = senderAvatarUrl,
size = AvatarSize.SMALL size = AvatarSize.SMALL
) )
@ -67,15 +67,15 @@ class TimelineItemEventFactory @Inject constructor(
senderId = currentSender, senderId = currentSender,
senderDisplayName = senderDisplayName, senderDisplayName = senderDisplayName,
senderAvatar = senderAvatarData, senderAvatar = senderAvatarData,
content = contentFactory.create(currentTimelineItem.event.content()), content = contentFactory.create(currentTimelineItem.event.content),
isMine = currentTimelineItem.event.isOwn(), isMine = currentTimelineItem.event.isOwn,
groupPosition = groupPosition, groupPosition = groupPosition,
reactionsState = currentTimelineItem.computeReactionsState() reactionsState = currentTimelineItem.computeReactionsState()
) )
} }
private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions { private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions {
val aggregatedReactions = event.reactions().orEmpty().map { val aggregatedReactions = event.reactions.map {
AggregatedReaction(key = it.key, count = it.count.toString(), isHighlighted = false) AggregatedReaction(key = it.key, count = it.count.toString(), isHighlighted = false)
} }
return TimelineItemReactions(aggregatedReactions.toImmutableList()) return TimelineItemReactions(aggregatedReactions.toImmutableList())
@ -90,9 +90,9 @@ class TimelineItemEventFactory @Inject constructor(
timelineItems.getOrNull(index - 1) as? MatrixTimelineItem.Event timelineItems.getOrNull(index - 1) as? MatrixTimelineItem.Event
val nextTimelineItem = val nextTimelineItem =
timelineItems.getOrNull(index + 1) as? MatrixTimelineItem.Event timelineItems.getOrNull(index + 1) as? MatrixTimelineItem.Event
val currentSender = currentTimelineItem.event.sender() val currentSender = currentTimelineItem.event.sender
val previousSender = prevTimelineItem?.event?.sender() val previousSender = prevTimelineItem?.event?.sender
val nextSender = nextTimelineItem?.event?.sender() val nextSender = nextTimelineItem?.event?.sender
return when { return when {
previousSender != currentSender && nextSender == currentSender -> TimelineItemGroupPosition.First previousSender != currentSender && nextSender == currentSender -> TimelineItemGroupPosition.First

View file

@ -19,13 +19,13 @@ package io.element.android.features.messages.impl.timeline.factories.virtual
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModel
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter import io.element.android.libraries.dateformatter.api.DaySeparatorFormatter
import org.matrix.rustcomponents.sdk.VirtualTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
import javax.inject.Inject import javax.inject.Inject
class TimelineItemDaySeparatorFactory @Inject constructor(private val daySeparatorFormatter: DaySeparatorFormatter) { class TimelineItemDaySeparatorFactory @Inject constructor(private val daySeparatorFormatter: DaySeparatorFormatter) {
fun create(virtualItem: VirtualTimelineItem.DayDivider): TimelineItemVirtualModel { fun create(virtualItem: VirtualTimelineItem.DayDivider): TimelineItemVirtualModel {
val formattedDate = daySeparatorFormatter.format(virtualItem.ts.toLong()) val formattedDate = daySeparatorFormatter.format(virtualItem.timestamp)
return TimelineItemDaySeparatorModel( return TimelineItemDaySeparatorModel(
formattedDate = formattedDate formattedDate = formattedDate
) )

View file

@ -22,7 +22,7 @@ import io.element.android.features.messages.impl.timeline.model.virtual.Timeline
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemUnknownVirtualModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemUnknownVirtualModel
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import org.matrix.rustcomponents.sdk.VirtualTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
import javax.inject.Inject import javax.inject.Inject
class TimelineItemVirtualFactory @Inject constructor( class TimelineItemVirtualFactory @Inject constructor(

View file

@ -21,11 +21,12 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
@Immutable @Immutable
sealed interface TimelineItem { sealed interface TimelineItem {
fun identifier(): String = when(this){ fun identifier(): String = when (this) {
is Event -> id is Event -> id
is Virtual -> id is Virtual -> id
} }
@ -40,7 +41,7 @@ sealed interface TimelineItem {
data class Event( data class Event(
val id: String, val id: String,
val eventId: EventId? = null, val eventId: EventId? = null,
val senderId: String, val senderId: UserId,
val senderDisplayName: String?, val senderDisplayName: String?,
val senderAvatar: AvatarData, val senderAvatar: AvatarData,
val content: TimelineItemEventContent, val content: TimelineItemEventContent,
@ -52,6 +53,6 @@ sealed interface TimelineItem {
val showSenderInformation = groupPosition.isNew() && !isMine val showSenderInformation = groupPosition.isNew() && !isMine
val safeSenderName: String = senderDisplayName ?: senderId val safeSenderName: String = senderDisplayName ?: senderId.value
} }
} }

View file

@ -16,8 +16,8 @@
package io.element.android.features.messages.impl.timeline.model.event package io.element.android.features.messages.impl.timeline.model.event
import org.matrix.rustcomponents.sdk.EncryptedMessage import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
data class TimelineItemEncryptedContent( data class TimelineItemEncryptedContent(
val encryptedMessage: EncryptedMessage val data: UnableToDecryptContent.Data
) : TimelineItemEventContent ) : TimelineItemEventContent

View file

@ -17,8 +17,8 @@
package io.element.android.features.messages.impl.timeline.model.event package io.element.android.features.messages.impl.timeline.model.event
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.matrix.rustcomponents.sdk.EncryptedMessage
class TimelineItemEventContentProvider : PreviewParameterProvider<TimelineItemEventContent> { class TimelineItemEventContentProvider : PreviewParameterProvider<TimelineItemEventContent> {
override val values = sequenceOf( override val values = sequenceOf(
@ -49,7 +49,7 @@ fun aTimelineItemEmoteContent() = TimelineItemEmoteContent(
) )
fun aTimelineItemEncryptedContent() = TimelineItemEncryptedContent( fun aTimelineItemEncryptedContent() = TimelineItemEncryptedContent(
encryptedMessage = EncryptedMessage.Unknown data = UnableToDecryptContent.Data.Unknown
) )
fun aTimelineItemNoticeContent() = TimelineItemNoticeContent( fun aTimelineItemNoticeContent() = TimelineItemNoticeContent(

View file

@ -30,7 +30,7 @@ open class TimelineItemImageContentProvider : PreviewParameterProvider<TimelineI
fun aTimelineItemImageContent() = TimelineItemImageContent( fun aTimelineItemImageContent() = TimelineItemImageContent(
body = "a body", body = "a body",
imageMeta = MediaResolver.Meta(source = null, kind = MediaResolver.Kind.Content), imageMeta = MediaResolver.Meta(url = null, kind = MediaResolver.Kind.Content),
blurhash = null, blurhash = null,
aspectRatio = 0.5f, aspectRatio = 0.5f,
) )

View file

@ -16,10 +16,10 @@
package io.element.android.features.messages.impl.timeline.util package io.element.android.features.messages.impl.timeline.util
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.matrix.rustcomponents.sdk.FormattedBody
import org.matrix.rustcomponents.sdk.MessageFormat
fun FormattedBody.toHtmlDocument(): Document? { fun FormattedBody.toHtmlDocument(): Document? {
return takeIf { it.format == MessageFormat.HTML }?.body?.let { formattedBody -> return takeIf { it.format == MessageFormat.HTML }?.body?.let { formattedBody ->

View file

@ -170,7 +170,7 @@ private fun aMessageEvent(
) = TimelineItem.Event( ) = TimelineItem.Event(
id = AN_EVENT_ID.value, id = AN_EVENT_ID.value,
eventId = AN_EVENT_ID, eventId = AN_EVENT_ID,
senderId = A_USER_ID.value, senderId = A_USER_ID,
senderDisplayName = A_USER_NAME, senderDisplayName = A_USER_NAME,
senderAvatar = AvatarData(A_USER_ID.value, A_USER_NAME), senderAvatar = AvatarData(A_USER_ID.value, A_USER_NAME),
content = content, content = content,

View file

@ -33,7 +33,7 @@ internal fun aMessageEvent(
) = TimelineItem.Event( ) = TimelineItem.Event(
id = AN_EVENT_ID.value, id = AN_EVENT_ID.value,
eventId = AN_EVENT_ID, eventId = AN_EVENT_ID,
senderId = A_USER_ID.value, senderId = A_USER_ID,
senderDisplayName = A_USER_NAME, senderDisplayName = A_USER_NAME,
senderAvatar = AvatarData(A_USER_ID.value, A_USER_NAME), senderAvatar = AvatarData(A_USER_ID.value, A_USER_NAME),
content = content, content = content,

View file

@ -58,7 +58,7 @@ class RoomListPresenter @Inject constructor(
} }
var filter by rememberSaveable { mutableStateOf("") } var filter by rememberSaveable { mutableStateOf("") }
val roomSummaries by client val roomSummaries by client
.roomSummaryDataSource() .roomSummaryDataSource
.roomSummaries() .roomSummaries()
.collectAsState() .collectAsState()
@ -125,7 +125,7 @@ class RoomListPresenter @Inject constructor(
// Safe to give bigger size than room list // Safe to give bigger size than room list
val extendedRangeEnd = range.last + midExtendedRangeSize val extendedRangeEnd = range.last + midExtendedRangeSize
val extendedRange = IntRange(extendedRangeStart, extendedRangeEnd) val extendedRange = IntRange(extendedRangeStart, extendedRangeEnd)
client.roomSummaryDataSource().setSlidingSyncRange(extendedRange) client.roomSummaryDataSource.setSlidingSyncRange(extendedRange)
} }
private suspend fun mapRoomSummaries( private suspend fun mapRoomSummaries(

View file

@ -14,23 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.matrix.impl.di package io.element.android.libraries.core.extensions
import com.squareup.anvil.annotations.ContributesTo /**
import dagger.Module * Can be used to transform some Throwable into some other.
import dagger.Provides */
import io.element.android.libraries.di.AppScope inline fun <R, T : R> Result<T>.mapFailure(transform: (exception: Throwable) -> Throwable): Result<R> {
import io.element.android.libraries.di.SingleIn return when (val exception = exceptionOrNull()) {
import org.matrix.rustcomponents.sdk.AuthenticationService null -> this
import java.io.File else -> Result.failure(transform(exception))
@Module
@ContributesTo(AppScope::class)
object MatrixModule {
@Provides
@SingleIn(AppScope::class)
fun providesRustAuthenticationService(baseDirectory: File): AuthenticationService {
return AuthenticationService(baseDirectory.absolutePath, null, null)
} }
} }

View file

@ -32,8 +32,6 @@ anvil {
} }
dependencies { dependencies {
// api(projects.libraries.rustsdk)
api(libs.matrix.sdk)
implementation(projects.libraries.di) implementation(projects.libraries.di)
implementation(libs.dagger) implementation(libs.dagger)
implementation(projects.libraries.core) implementation(projects.libraries.core)

View file

@ -21,22 +21,20 @@ import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.media.MediaResolver import io.element.android.libraries.matrix.api.media.MediaResolver
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import org.matrix.rustcomponents.sdk.MediaSource
import java.io.Closeable
interface MatrixClient : Closeable { interface MatrixClient {
val sessionId: SessionId val sessionId: SessionId
val roomSummaryDataSource: RoomSummaryDataSource
fun getRoom(roomId: RoomId): MatrixRoom? fun getRoom(roomId: RoomId): MatrixRoom?
fun startSync() fun startSync()
fun stopSync() fun stopSync()
fun roomSummaryDataSource(): RoomSummaryDataSource
fun mediaResolver(): MediaResolver fun mediaResolver(): MediaResolver
suspend fun logout() suspend fun logout()
suspend fun loadUserDisplayName(): Result<String> suspend fun loadUserDisplayName(): Result<String>
suspend fun loadUserAvatarURLString(): Result<String> suspend fun loadUserAvatarURLString(): Result<String>
suspend fun loadMediaContentForSource(source: MediaSource): Result<ByteArray> suspend fun loadMediaContent(url: String): Result<ByteArray>
suspend fun loadMediaThumbnailForSource( suspend fun loadMediaThumbnail(
source: MediaSource, url: String,
width: Long, width: Long,
height: Long height: Long
): Result<ByteArray> ): Result<ByteArray>

View file

@ -16,8 +16,6 @@
package io.element.android.libraries.matrix.api.auth package io.element.android.libraries.matrix.api.auth
import org.matrix.rustcomponents.sdk.AuthenticationException
enum class AuthErrorCode(val value: String) { enum class AuthErrorCode(val value: String) {
UNKNOWN("M_UNKNOWN"), UNKNOWN("M_UNKNOWN"),
USER_DEACTIVATED("M_USER_DEACTIVATED"), USER_DEACTIVATED("M_USER_DEACTIVATED"),

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.auth
sealed class AuthenticationException(message: String) : Exception(message) {
class ClientMissing(message: String) : AuthenticationException(message)
class InvalidServerName(message: String) : AuthenticationException(message)
class SlidingSyncNotAvailable(message: String) : AuthenticationException(message)
class SessionMissing(message: String) : AuthenticationException(message)
class Generic(message: String) : AuthenticationException(message)
}

View file

@ -24,8 +24,8 @@ import kotlinx.coroutines.flow.StateFlow
interface MatrixAuthenticationService { interface MatrixAuthenticationService {
fun isLoggedIn(): Flow<Boolean> fun isLoggedIn(): Flow<Boolean>
suspend fun getLatestSessionId(): SessionId? suspend fun getLatestSessionId(): SessionId?
suspend fun restoreSession(sessionId: SessionId): MatrixClient? suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient>
fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?> fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?>
suspend fun setHomeserver(homeserver: String) suspend fun setHomeserver(homeserver: String): Result<Unit>
suspend fun login(username: String, password: String): SessionId suspend fun login(username: String, password: String): Result<SessionId>
} }

View file

@ -18,17 +18,10 @@ package io.element.android.libraries.matrix.api.auth
import android.os.Parcelable import android.os.Parcelable
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.rustcomponents.sdk.HomeserverLoginDetails
@Parcelize @Parcelize
data class MatrixHomeServerDetails( data class MatrixHomeServerDetails(
val url: String, val url: String,
val supportsPasswordLogin: Boolean, val supportsPasswordLogin: Boolean,
val authenticationIssuer: String? val authenticationIssuer: String?
): Parcelable { ): Parcelable
constructor(homeserverLoginDetails: HomeserverLoginDetails) : this(
homeserverLoginDetails.url(),
homeserverLoginDetails.supportsPasswordLogin(),
homeserverLoginDetails.authenticationIssuer()
)
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.media
data class AudioInfo(
val duration: Long?,
val size: Long?
)

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.media
data class FileInfo(
val mimetype: String?,
val size: Long?,
val thumbnailInfo: ThumbnailInfo?,
val thumbnailUrl: String?
)

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.media
data class ImageInfo(
val height: Long?,
val width: Long?,
val mimetype: String?,
val size: Long?,
val thumbnailInfo: ThumbnailInfo?,
val thumbnailUrl: String?,
val blurhash: String?
)

View file

@ -16,8 +16,6 @@
package io.element.android.libraries.matrix.api.media package io.element.android.libraries.matrix.api.media
import org.matrix.rustcomponents.sdk.MediaSource
interface MediaResolver { interface MediaResolver {
sealed interface Kind { sealed interface Kind {
@ -29,11 +27,10 @@ interface MediaResolver {
} }
data class Meta( data class Meta(
val source: MediaSource?, val url: String?,
val kind: Kind val kind: Kind
) )
suspend fun resolve(url: String?, kind: Kind): ByteArray? suspend fun resolve(url: String?, kind: Kind): ByteArray?
suspend fun resolve(meta: Meta): ByteArray?
} }

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.media
data class ThumbnailInfo(
val height: Long?,
val width: Long?,
val mimetype: String?,
val size: Long?
)

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.media
data class VideoInfo(
val duration: Long?,
val height: Long?,
val width: Long?,
val mimetype: String?,
val size: Long?,
val thumbnailInfo: ThumbnailInfo?,
val thumbnailUrl: String?,
val blurhash: String?
)

View file

@ -20,8 +20,9 @@ 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.RoomId
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import java.io.Closeable
interface MatrixRoom { interface MatrixRoom: Closeable {
val roomId: RoomId val roomId: RoomId
val name: String? val name: String?
val bestName: String val bestName: String

View file

@ -17,13 +17,13 @@
package io.element.android.libraries.matrix.api.timeline package io.element.android.libraries.matrix.api.timeline
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import org.matrix.rustcomponents.sdk.EventTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import org.matrix.rustcomponents.sdk.VirtualTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
sealed interface MatrixTimelineItem { sealed interface MatrixTimelineItem {
data class Event(val event: EventTimelineItem) : MatrixTimelineItem { data class Event(val event: EventTimelineItem) : MatrixTimelineItem {
val uniqueId: String = event.uniqueIdentifier() val uniqueId: String = event.uniqueIdentifier
val eventId: EventId? = event.eventId()?.let { EventId(it) } val eventId: EventId? = event.eventId
} }
data class Virtual(val virtual: VirtualTimelineItem) : MatrixTimelineItem data class Virtual(val virtual: VirtualTimelineItem) : MatrixTimelineItem

View file

@ -0,0 +1,212 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.timeline.item.event
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.media.FileInfo
import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.VideoInfo
sealed interface EventContent
data class MessageContent(
val body: String,
val inReplyTo: UserId?,
val isEdited: Boolean,
val type: MessageType?
) : EventContent
object RedactedContent : EventContent
data class StickerContent(
val body: String,
val info: ImageInfo,
val url: String
) : EventContent
data class UnableToDecryptContent(
val data: Data
) : EventContent {
sealed interface Data {
data class OlmV1Curve25519AesSha2(
val senderKey: String
) : Data
data class MegolmV1AesSha2(
val sessionId: String
) : Data
object Unknown : Data
}
}
data class RoomMembershipContent(
val userId: UserId,
val change: MembershipChange?
) : EventContent
data class ProfileChangeContent(
val displayName: String?,
val prevDisplayName: String?,
val avatarUrl: String?,
val prevAvatarUrl: String?
) : EventContent
data class StateContent(
val stateKey: String,
val content: OtherState
) : EventContent
data class FailedToParseMessageLikeContent(
val eventType: String,
val error: String
) : EventContent
data class FailedToParseStateContent(
val eventType: String,
val stateKey: String,
val error: String
) : EventContent
object UnknownContent : EventContent
sealed interface MessageType
object UnknownMessageType : MessageType
enum class MessageFormat {
HTML, UNKNOWN
}
data class FormattedBody(
val format: MessageFormat,
val body: String
)
data class EmoteMessageType(
val body: String,
val formatted: FormattedBody?
) : MessageType
data class ImageMessageType(
val body: String,
val url: String,
val info: ImageInfo?
) : MessageType
data class AudioMessageType(
var body: String,
var url: String,
var info: AudioInfo?
) : MessageType
data class VideoMessageType(
val body: String,
val url: String,
val info: VideoInfo?
) : MessageType
data class FileMessageType(
val body: String,
val url: String,
val info: FileInfo?
) : MessageType
data class NoticeMessageType(
val body: String,
val formatted: FormattedBody?
) : MessageType
data class TextMessageType(
val body: String,
val formatted: FormattedBody?
) : MessageType
enum class MembershipChange {
NONE,
ERROR,
JOINED,
LEFT,
BANNED,
UNBANNED,
KICKED,
INVITED,
KICKED_AND_BANNED,
INVITATION_ACCEPTED,
INVITATION_REJECTED,
INVITATION_REVOKED,
KNOCKED,
KNOCK_ACCEPTED,
KNOCK_RETRACTED,
KNOCK_DENIED,
NOT_IMPLEMENTED;
}
sealed interface OtherState {
object PolicyRuleRoom : OtherState
object PolicyRuleServer : OtherState
object PolicyRuleUser : OtherState
object RoomAliases : OtherState
data class RoomAvatar(
val url: String?
) : OtherState
object RoomCanonicalAlias : OtherState
object RoomCreate : OtherState
object RoomEncryption : OtherState
object RoomGuestAccess : OtherState
object RoomHistoryVisibility : OtherState
object RoomJoinRules : OtherState
data class RoomName(
val name: String?
) : OtherState
object RoomPinnedEvents : OtherState
object RoomPowerLevels : OtherState
object RoomServerAcl : OtherState
data class RoomThirdPartyInvite(
val displayName: String?
) : OtherState
object RoomTombstone : OtherState
data class RoomTopic(
val topic: String?
) : OtherState
object SpaceChild : OtherState
object SpaceParent : OtherState
data class Custom(
val eventType: String
) : OtherState
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.timeline.item.event
data class EventReaction(
val key: String,
val count: Long
)

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.timeline.item.event
import io.element.android.libraries.matrix.api.core.EventId
sealed interface EventSendState {
object NotSendYet : EventSendState
data class SendingFailed(
val error: String
) : EventSendState
data class Sent(
val eventId: EventId
) : EventSendState
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.timeline.item.event
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
data class EventTimelineItem(
val uniqueIdentifier: String,
val eventId: EventId?,
val isEditable: Boolean,
val isLocal: Boolean,
val isOwn: Boolean,
val isRemote: Boolean,
val localSendState: EventSendState?,
val reactions: List<EventReaction>,
val sender: UserId,
val senderProfile: ProfileTimelineDetails,
val timestamp: Long,
val content: EventContent
)

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.timeline.item.event
sealed interface ProfileTimelineDetails {
object Unavailable : ProfileTimelineDetails
object Pending : ProfileTimelineDetails
data class Ready(
val displayName: String?,
val displayNameAmbiguous: Boolean,
val avatarUrl: String?
) : ProfileTimelineDetails
data class Error(
val message: String
) : ProfileTimelineDetails
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.api.timeline.item.virtual
sealed interface VirtualTimelineItem {
data class DayDivider(
val timestamp: Long
) : VirtualTimelineItem
object ReadMarker : VirtualTimelineItem
object LoadingIndicator : VirtualTimelineItem
object TimelineStart : VirtualTimelineItem
}

View file

@ -18,7 +18,6 @@ package io.element.android.libraries.matrix.api.auth
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Test import org.junit.Test
import org.matrix.rustcomponents.sdk.AuthenticationException
class AuthErrorCodeTests { class AuthErrorCodeTests {

View file

@ -32,7 +32,7 @@ anvil {
dependencies { dependencies {
// api(projects.libraries.rustsdk) // api(projects.libraries.rustsdk)
api(libs.matrix.sdk) implementation(libs.matrix.sdk)
implementation(projects.libraries.di) implementation(projects.libraries.di)
implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api)
implementation(libs.dagger) implementation(libs.dagger)

View file

@ -20,24 +20,25 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.MatrixClient 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.RoomId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.MediaResolver
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.impl.media.RustMediaResolver import io.element.android.libraries.matrix.impl.media.RustMediaResolver
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource
import io.element.android.libraries.matrix.impl.sync.SlidingSyncObserverProxy import io.element.android.libraries.matrix.impl.sync.SlidingSyncObserverProxy
import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.matrix.api.media.MediaResolver
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientDelegate import org.matrix.rustcomponents.sdk.ClientDelegate
import org.matrix.rustcomponents.sdk.MediaSource
import org.matrix.rustcomponents.sdk.RequiredState import org.matrix.rustcomponents.sdk.RequiredState
import org.matrix.rustcomponents.sdk.SlidingSyncMode import org.matrix.rustcomponents.sdk.SlidingSyncMode
import org.matrix.rustcomponents.sdk.SlidingSyncRequestListFilters import org.matrix.rustcomponents.sdk.SlidingSyncRequestListFilters
import org.matrix.rustcomponents.sdk.SlidingSyncViewBuilder import org.matrix.rustcomponents.sdk.SlidingSyncViewBuilder
import org.matrix.rustcomponents.sdk.TaskHandle import org.matrix.rustcomponents.sdk.TaskHandle
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
import org.matrix.rustcomponents.sdk.use
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -94,7 +95,9 @@ class RustMatrixClient constructor(
.sendUpdatesForItems(true) .sendUpdatesForItems(true)
.syncMode(mode = SlidingSyncMode.SELECTIVE) .syncMode(mode = SlidingSyncMode.SELECTIVE)
.addRange(0u, 20u) .addRange(0u, 20u)
.build() .use {
it.build()
}
private val slidingSync = client private val slidingSync = client
.slidingSync() .slidingSync()
@ -102,10 +105,12 @@ class RustMatrixClient constructor(
.withCommonExtensions() .withCommonExtensions()
.coldCache("ElementX") .coldCache("ElementX")
.addView(visibleRoomsView) .addView(visibleRoomsView)
.build() .use {
it.build()
}
private val slidingSyncObserverProxy = SlidingSyncObserverProxy(coroutineScope) private val slidingSyncObserverProxy = SlidingSyncObserverProxy(coroutineScope)
private val roomSummaryDataSource: RustRoomSummaryDataSource = private val rustRoomSummaryDataSource: RustRoomSummaryDataSource =
RustRoomSummaryDataSource( RustRoomSummaryDataSource(
slidingSyncObserverProxy.updateSummaryFlow, slidingSyncObserverProxy.updateSummaryFlow,
slidingSync, slidingSync,
@ -113,6 +118,10 @@ class RustMatrixClient constructor(
dispatchers, dispatchers,
::onRestartSync ::onRestartSync
) )
override val roomSummaryDataSource: RoomSummaryDataSource
get() = rustRoomSummaryDataSource
private var slidingSyncObserverToken: TaskHandle? = null private var slidingSyncObserverToken: TaskHandle? = null
private val mediaResolver = RustMediaResolver(this) private val mediaResolver = RustMediaResolver(this)
@ -120,7 +129,7 @@ class RustMatrixClient constructor(
init { init {
client.setDelegate(clientDelegate) client.setDelegate(clientDelegate)
roomSummaryDataSource.init() rustRoomSummaryDataSource.init()
slidingSync.setObserver(slidingSyncObserverProxy) slidingSync.setObserver(slidingSyncObserverProxy)
} }
@ -141,8 +150,6 @@ class RustMatrixClient constructor(
) )
} }
override fun roomSummaryDataSource(): RoomSummaryDataSource = roomSummaryDataSource
override fun mediaResolver(): MediaResolver = mediaResolver override fun mediaResolver(): MediaResolver = mediaResolver
override fun startSync() { override fun startSync() {
@ -154,15 +161,17 @@ class RustMatrixClient constructor(
override fun stopSync() { override fun stopSync() {
if (isSyncing.compareAndSet(true, false)) { if (isSyncing.compareAndSet(true, false)) {
slidingSyncObserverToken?.cancel() slidingSyncObserverToken?.use { it.cancel() }
} }
} }
override fun close() { private fun close() {
stopSync() stopSync()
slidingSync.setObserver(null) slidingSync.setObserver(null)
roomSummaryDataSource.close() rustRoomSummaryDataSource.close()
client.setDelegate(null) client.setDelegate(null)
visibleRoomsView.destroy()
slidingSync.destroy()
} }
override suspend fun logout() = withContext(dispatchers.io) { override suspend fun logout() = withContext(dispatchers.io) {
@ -174,6 +183,7 @@ class RustMatrixClient constructor(
} }
baseDirectory.deleteSessionDirectory(userID = client.userId()) baseDirectory.deleteSessionDirectory(userID = client.userId())
sessionStore.removeSession(client.userId()) sessionStore.removeSession(client.userId())
client.destroy()
} }
override suspend fun loadUserDisplayName(): Result<String> = withContext(dispatchers.io) { override suspend fun loadUserDisplayName(): Result<String> = withContext(dispatchers.io) {
@ -189,23 +199,30 @@ class RustMatrixClient constructor(
} }
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
override suspend fun loadMediaContentForSource(source: MediaSource): Result<ByteArray> = override suspend fun loadMediaContent(url: String): Result<ByteArray> =
withContext(dispatchers.io) { withContext(dispatchers.io) {
runCatching { runCatching {
client.getMediaContent(source).toUByteArray().toByteArray() mediaSourceFromUrl(url).use { source ->
client.getMediaContent(source).toUByteArray().toByteArray()
}
} }
} }
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
override suspend fun loadMediaThumbnailForSource( override suspend fun loadMediaThumbnail(
source: MediaSource, url: String,
width: Long, width: Long,
height: Long height: Long
): Result<ByteArray> = ): Result<ByteArray> =
withContext(dispatchers.io) { withContext(dispatchers.io) {
runCatching { runCatching {
client.getMediaThumbnail(source, width.toULong(), height.toULong()).toUByteArray() mediaSourceFromUrl(url).use { source ->
.toByteArray() client.getMediaThumbnail(
source = source,
width = width.toULong(),
height = height.toULong()
).toUByteArray().toByteArray()
}
} }
} }

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.auth
import io.element.android.libraries.matrix.api.auth.AuthenticationException
import org.matrix.rustcomponents.sdk.AuthenticationException as RustAuthenticationException
fun Throwable.mapAuthenticationException(): Throwable {
return when (this) {
is RustAuthenticationException.ClientMissing -> AuthenticationException.ClientMissing(this.message!!)
is RustAuthenticationException.Generic -> AuthenticationException.Generic(this.message!!)
is RustAuthenticationException.InvalidServerName -> AuthenticationException.InvalidServerName(this.message!!)
is RustAuthenticationException.SessionMissing -> AuthenticationException.SessionMissing(this.message!!)
is RustAuthenticationException.SlidingSyncNotAvailable -> AuthenticationException.SlidingSyncNotAvailable(this.message!!)
else -> this
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.auth
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import org.matrix.rustcomponents.sdk.HomeserverLoginDetails
fun HomeserverLoginDetails.map(): MatrixHomeServerDetails = use {
MatrixHomeServerDetails(
url = url(),
supportsPasswordLogin = supportsPasswordLogin(),
authenticationIssuer = authenticationIssuer()
)
}

View file

@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.impl.auth
import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.SingleIn import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
@ -26,7 +27,6 @@ import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import io.element.android.libraries.matrix.api.core.SessionId 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.core.UserId
import io.element.android.libraries.matrix.impl.RustMatrixClient import io.element.android.libraries.matrix.impl.RustMatrixClient
import io.element.android.libraries.matrix.impl.util.logError
import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionData
import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.api.SessionStore
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -34,14 +34,13 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.AuthenticationService
import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientBuilder import org.matrix.rustcomponents.sdk.ClientBuilder
import org.matrix.rustcomponents.sdk.Session import org.matrix.rustcomponents.sdk.Session
import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.use
import timber.log.Timber
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
import org.matrix.rustcomponents.sdk.AuthenticationService as RustAuthenticationService
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
@SingleIn(AppScope::class) @SingleIn(AppScope::class)
@ -50,9 +49,9 @@ class RustMatrixAuthenticationService @Inject constructor(
private val coroutineScope: CoroutineScope, private val coroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
private val sessionStore: SessionStore, private val sessionStore: SessionStore,
private val authService: AuthenticationService,
) : MatrixAuthenticationService { ) : MatrixAuthenticationService {
private val authService: RustAuthenticationService = RustAuthenticationService(baseDirectory.absolutePath, null, null)
private var currentHomeserver = MutableStateFlow<MatrixHomeServerDetails?>(null) private var currentHomeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
override fun isLoggedIn(): Flow<Boolean> { override fun isLoggedIn(): Flow<Boolean> {
@ -63,10 +62,10 @@ class RustMatrixAuthenticationService @Inject constructor(
sessionStore.getLatestSession()?.userId?.let { UserId(it) } sessionStore.getLatestSession()?.userId?.let { UserId(it) }
} }
override suspend fun restoreSession(sessionId: SessionId) = withContext(coroutineDispatchers.io) { override suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient> = withContext(coroutineDispatchers.io) {
val sessionData = sessionStore.getSession(sessionId.value) runCatching {
if (sessionData != null) { val sessionData = sessionStore.getSession(sessionId.value)
try { if (sessionData != null) {
val client = ClientBuilder() val client = ClientBuilder()
.basePath(baseDirectory.absolutePath) .basePath(baseDirectory.absolutePath)
.homeserverUrl(sessionData.homeserverUrl) .homeserverUrl(sessionData.homeserverUrl)
@ -74,36 +73,39 @@ class RustMatrixAuthenticationService @Inject constructor(
.use { it.build() } .use { it.build() }
client.restoreSession(sessionData.toSession()) client.restoreSession(sessionData.toSession())
createMatrixClient(client) createMatrixClient(client)
} catch (throwable: Throwable) { } else {
logError(throwable) throw IllegalStateException("No session to restore with id $sessionId")
null
} }
} else null }.mapFailure { failure ->
failure.mapAuthenticationException()
}
} }
override fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?> = currentHomeserver override fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?> = currentHomeserver
override suspend fun setHomeserver(homeserver: String) { override suspend fun setHomeserver(homeserver: String): Result<Unit> =
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
authService.configureHomeserver(homeserver) runCatching {
val homeServerDetails = authService.homeserverDetails()?.use { MatrixHomeServerDetails(it) } authService.configureHomeserver(homeserver)
if (homeServerDetails != null) { val homeServerDetails = authService.homeserverDetails()?.map()
currentHomeserver.value = homeServerDetails.copy(url = homeserver) if (homeServerDetails != null) {
currentHomeserver.value = homeServerDetails.copy(url = homeserver)
}
} }
}.mapFailure { failure ->
failure.mapAuthenticationException()
} }
}
override suspend fun login(username: String, password: String): SessionId = override suspend fun login(username: String, password: String): Result<SessionId> =
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
val client = try { runCatching {
authService.login(username, password, "ElementX Android", null) val client = authService.login(username, password, "ElementX Android", null)
} catch (failure: Throwable) { val sessionData = client.use { it.session().toSessionData() }
Timber.e(failure, "Fail login") sessionStore.storeData(sessionData)
throw failure SessionId(sessionData.userId)
} }
val sessionData = client.use { it.session().toSessionData() } }.mapFailure { failure ->
sessionStore.storeData(sessionData) failure.mapAuthenticationException()
SessionId(sessionData.userId)
} }
private fun createMatrixClient(client: Client): MatrixClient { private fun createMatrixClient(client: Client): MatrixClient {

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.matrix.api.media.AudioInfo
import org.matrix.rustcomponents.sdk.AudioInfo as RustAudioInfo
fun RustAudioInfo.map(): AudioInfo = AudioInfo(
duration = duration?.toLong(),
size = size?.toLong()
)

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.matrix.api.media.FileInfo
import io.element.android.libraries.matrix.api.media.ThumbnailInfo
import org.matrix.rustcomponents.sdk.FileInfo as RustFileInfo
import org.matrix.rustcomponents.sdk.ThumbnailInfo as RustThumbnailInfo
fun RustFileInfo.map(): FileInfo = FileInfo(
mimetype = mimetype,
size = size?.toLong(),
thumbnailInfo = thumbnailInfo?.map(),
thumbnailUrl = thumbnailSource?.useUrl()
)

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.matrix.api.media.ImageInfo
import org.matrix.rustcomponents.sdk.ImageInfo as RustImageInfo
fun RustImageInfo.map(): ImageInfo = ImageInfo(
height = height?.toLong(),
width = width?.toLong(),
mimetype = mimetype,
size = size?.toLong(),
thumbnailInfo = thumbnailInfo?.map(),
thumbnailUrl = thumbnailSource?.useUrl(),
blurhash = blurhash
)

View file

@ -14,19 +14,23 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.libraries.matrix.impl.timeline package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem interface MediaResolver {
import org.matrix.rustcomponents.sdk.TimelineItem
fun TimelineItem.asMatrixTimelineItem(): MatrixTimelineItem { sealed interface Kind {
val asEvent = asEvent() data class Thumbnail(val width: Int, val height: Int) : Kind {
if (asEvent != null) { constructor(size: Int) : this(size, size)
return MatrixTimelineItem.Event(asEvent) }
object Content : Kind
} }
val asVirtual = asVirtual()
if (asVirtual != null) { data class Meta(
return MatrixTimelineItem.Virtual(asVirtual) val url: String?,
} val kind: Kind
return MatrixTimelineItem.Other )
suspend fun resolve(url: String?, kind: Kind): ByteArray?
} }

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.media
import org.matrix.rustcomponents.sdk.MediaSource
import org.matrix.rustcomponents.sdk.use
fun MediaSource.useUrl(): String = use { it.url() }

View file

@ -18,23 +18,15 @@ package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.media.MediaResolver import io.element.android.libraries.matrix.api.media.MediaResolver
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
internal class RustMediaResolver(private val client: MatrixClient) : MediaResolver { internal class RustMediaResolver(private val client: MatrixClient) : MediaResolver {
override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? { override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? {
if (url.isNullOrEmpty()) return null if (url.isNullOrEmpty()) return null
val mediaSource = mediaSourceFromUrl(url)
return resolve(MediaResolver.Meta(mediaSource, kind))
}
override suspend fun resolve(meta: MediaResolver.Meta): ByteArray? {
val source = meta.source ?: return null
val kind = meta.kind
return when (kind) { return when (kind) {
is MediaResolver.Kind.Content -> client.loadMediaContentForSource(source) is MediaResolver.Kind.Content -> client.loadMediaContent(url)
is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnailForSource( is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnail(
source, url,
kind.width.toLong(), kind.width.toLong(),
kind.height.toLong() kind.height.toLong()
) )

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.matrix.api.media.ThumbnailInfo
import org.matrix.rustcomponents.sdk.ThumbnailInfo as RustThumbnailInfo
fun RustThumbnailInfo.map(): ThumbnailInfo = ThumbnailInfo(
height = height?.toLong(),
width = width?.toLong(),
mimetype = mimetype,
size = size?.toLong()
)

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.media
import io.element.android.libraries.matrix.api.media.VideoInfo
import org.matrix.rustcomponents.sdk.VideoInfo as RustVideoInfo
fun RustVideoInfo.map(): VideoInfo = VideoInfo(
duration = duration?.toLong(),
height = height?.toLong(),
width = width?.toLong(),
mimetype = mimetype,
size = size?.toLong(),
thumbnailInfo = thumbnailInfo?.map(),
thumbnailUrl = thumbnailSource?.useUrl(),
blurhash = blurhash
)

View file

@ -16,16 +16,16 @@
package io.element.android.libraries.matrix.impl.room package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomSummaryDetails import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.SlidingSyncRoom import org.matrix.rustcomponents.sdk.SlidingSyncRoom
class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory()) { class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory()) {
fun create(slidingSyncRoom: SlidingSyncRoom, room: Room?): RoomSummaryDetails { fun create(slidingSyncRoom: SlidingSyncRoom, room: Room?): RoomSummaryDetails {
val latestRoomMessage = slidingSyncRoom.latestRoomMessage()?.let { val latestRoomMessage = slidingSyncRoom.latestRoomMessage()?.use {
roomMessageFactory.create(it) roomMessageFactory.create(it)
} }
val computedLastMessage = when { val computedLastMessage = when {
@ -38,7 +38,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto
name = slidingSyncRoom.name() ?: slidingSyncRoom.roomId(), name = slidingSyncRoom.name() ?: slidingSyncRoom.roomId(),
isDirect = slidingSyncRoom.isDm() ?: false, isDirect = slidingSyncRoom.isDm() ?: false,
avatarURLString = room?.avatarUrl(), avatarURLString = room?.avatarUrl(),
unreadNotificationCount = slidingSyncRoom.unreadNotifications().notificationCount().toInt(), unreadNotificationCount = slidingSyncRoom.unreadNotifications().use { it.notificationCount().toInt() },
lastMessage = computedLastMessage, lastMessage = computedLastMessage,
lastMessageTimestamp = latestRoomMessage?.originServerTs lastMessageTimestamp = latestRoomMessage?.originServerTs
) )

View file

@ -17,11 +17,11 @@
package io.element.android.libraries.matrix.impl.room package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import io.element.android.libraries.matrix.api.core.EventId 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.RoomId
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
@ -62,6 +62,10 @@ class RustMatrixRoom(
coroutineDispatchers = coroutineDispatchers coroutineDispatchers = coroutineDispatchers
) )
} }
override fun close() {
innerRoom.destroy()
slidingSyncRoom.destroy()
}
override val roomId = RoomId(innerRoom.id()) override val roomId = RoomId(innerRoom.id())

View file

@ -17,10 +17,10 @@
package io.element.android.libraries.matrix.impl.room package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.impl.sync.roomListDiff
import io.element.android.libraries.matrix.impl.sync.state
import io.element.android.libraries.matrix.api.room.RoomSummary import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.impl.sync.roomListDiff
import io.element.android.libraries.matrix.impl.sync.state
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
@ -179,10 +179,14 @@ internal class RustRoomSummaryDataSource(
} }
private fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary { private fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary {
val room = slidingSync.getRoom(identifier) ?: return RoomSummary.Empty(identifier) val slidingSyncRoom = slidingSync.getRoom(identifier) ?: return RoomSummary.Empty(identifier)
return RoomSummary.Filled( val fullRoom = slidingSyncRoom.fullRoom()
details = roomSummaryDetailsFactory.create(room, room.fullRoom()) val roomSummary = RoomSummary.Filled(
details = roomSummaryDetailsFactory.create(slidingSyncRoom, fullRoom)
) )
fullRoom?.destroy()
slidingSyncRoom.destroy()
return roomSummary
} }
private suspend fun updateRoomSummaries(block: MutableList<RoomSummary>.() -> Unit) = private suspend fun updateRoomSummaries(block: MutableList<RoomSummary>.() -> Unit) =

View file

@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.impl.timeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -25,14 +26,15 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.TimelineChange import org.matrix.rustcomponents.sdk.TimelineChange
import org.matrix.rustcomponents.sdk.TimelineDiff import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineItem
import org.matrix.rustcomponents.sdk.TimelineListener import org.matrix.rustcomponents.sdk.TimelineListener
import org.matrix.rustcomponents.sdk.VirtualTimelineItem
internal class MatrixTimelineDiffProcessor( internal class MatrixTimelineDiffProcessor(
private val paginationState: MutableStateFlow<MatrixTimeline.PaginationState>, private val paginationState: MutableStateFlow<MatrixTimeline.PaginationState>,
private val timelineItems: MutableStateFlow<List<MatrixTimelineItem>>, private val timelineItems: MutableStateFlow<List<MatrixTimelineItem>>,
private val coroutineScope: CoroutineScope, private val coroutineScope: CoroutineScope,
private val diffDispatcher: CoroutineDispatcher, private val diffDispatcher: CoroutineDispatcher,
private val timelineItemFactory: MatrixTimelineItemMapper,
) : TimelineListener { ) : TimelineListener {
override fun onUpdate(update: TimelineDiff) { override fun onUpdate(update: TimelineDiff) {
@ -117,4 +119,9 @@ internal class MatrixTimelineDiffProcessor(
} }
} }
} }
private fun TimelineItem.asMatrixTimelineItem(): MatrixTimelineItem {
return timelineItemFactory.map(this)
}
} }

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.timeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper
import org.matrix.rustcomponents.sdk.TimelineItem
class MatrixTimelineItemMapper(
private val virtualTimelineItemMapper: VirtualTimelineItemMapper = VirtualTimelineItemMapper(),
private val eventTimelineItemMapper: EventTimelineItemMapper= EventTimelineItemMapper(),
) {
fun map(timelineItem: TimelineItem): MatrixTimelineItem = timelineItem.use {
val asEvent = timelineItem.asEvent()
if (asEvent != null) {
val eventTimelineItem = eventTimelineItemMapper.map(asEvent)
return MatrixTimelineItem.Event(eventTimelineItem)
}
val asVirtual = timelineItem.asVirtual()
if (asVirtual != null) {
val virtualTimelineItem = virtualTimelineItemMapper.map(asVirtual)
return MatrixTimelineItem.Virtual(virtualTimelineItem)
}
return MatrixTimelineItem.Other
}
}

View file

@ -17,11 +17,15 @@
package io.element.android.libraries.matrix.impl.timeline package io.element.android.libraries.matrix.impl.timeline
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.impl.util.TaskHandleBag
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessageMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper
import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper
import io.element.android.libraries.matrix.impl.util.TaskHandleBag
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -52,11 +56,21 @@ class RustMatrixTimeline(
MatrixTimeline.PaginationState(canBackPaginate = true, isBackPaginating = false) MatrixTimeline.PaginationState(canBackPaginate = true, isBackPaginating = false)
) )
private val timelineItemFactory = MatrixTimelineItemMapper(
virtualTimelineItemMapper = VirtualTimelineItemMapper(),
eventTimelineItemMapper = EventTimelineItemMapper(
contentMapper = TimelineEventContentMapper(
eventMessageMapper = EventMessageMapper()
)
)
)
private val innerTimelineListener = MatrixTimelineDiffProcessor( private val innerTimelineListener = MatrixTimelineDiffProcessor(
paginationState = paginationState, paginationState = paginationState,
timelineItems = timelineItems, timelineItems = timelineItems,
coroutineScope = coroutineScope, coroutineScope = coroutineScope,
diffDispatcher = coroutineDispatchers.diffUpdateDispatcher diffDispatcher = coroutineDispatchers.diffUpdateDispatcher,
timelineItemFactory = timelineItemFactory,
) )
private val listenerTokens = TaskHandleBag() private val listenerTokens = TaskHandleBag()
@ -83,7 +97,7 @@ class RustMatrixTimeline(
val result = addListener(innerTimelineListener) val result = addListener(innerTimelineListener)
result result
.onSuccess { timelineItems -> .onSuccess { timelineItems ->
val matrixTimelineItems = timelineItems.map { it.asMatrixTimelineItem() } val matrixTimelineItems = timelineItems.map(timelineItemFactory::map)
withContext(coroutineDispatchers.diffUpdateDispatcher) { withContext(coroutineDispatchers.diffUpdateDispatcher) {
this@RustMatrixTimeline.timelineItems.value = matrixTimelineItems this@RustMatrixTimeline.timelineItems.value = matrixTimelineItems
} }

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.timeline.item.event
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat
import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.media.useUrl
import org.matrix.rustcomponents.sdk.Message
import org.matrix.rustcomponents.sdk.MessageType
import org.matrix.rustcomponents.sdk.use
import org.matrix.rustcomponents.sdk.FormattedBody as RustFormattedBody
import org.matrix.rustcomponents.sdk.MessageFormat as RustMessageFormat
class EventMessageMapper {
fun map(message: Message): MessageContent = message.use {
val type = message.msgtype().use { type ->
when (type) {
is MessageType.Audio -> {
AudioMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map())
}
is MessageType.File -> {
FileMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map())
}
is MessageType.Image -> {
ImageMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map())
}
is MessageType.Notice -> {
NoticeMessageType(type.content.body, type.content.formatted?.map())
}
is MessageType.Text -> {
TextMessageType(type.content.body, type.content.formatted?.map())
}
is MessageType.Emote -> {
EmoteMessageType(type.content.body, type.content.formatted?.map())
}
is MessageType.Video -> {
VideoMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map())
}
null -> {
UnknownMessageType
}
}
}
MessageContent(
body = message.body(),
inReplyTo = message.inReplyTo()?.let { UserId(it) },
isEdited = message.isEdited(),
type = type
)
}
}
private fun RustFormattedBody.map(): FormattedBody = FormattedBody(
format = format.map(),
body = body
)
private fun RustMessageFormat.map(): MessageFormat {
return when (this) {
RustMessageFormat.HTML -> MessageFormat.HTML
RustMessageFormat.UNKNOWN -> MessageFormat.UNKNOWN
}
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.timeline.item.event
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails
import org.matrix.rustcomponents.sdk.Reaction
import org.matrix.rustcomponents.sdk.EventSendState as RustEventSendState
import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem
import org.matrix.rustcomponents.sdk.ProfileTimelineDetails as RustProfileTimelineDetails
class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper()) {
fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.use {
EventTimelineItem(
uniqueIdentifier = eventTimelineItem.uniqueIdentifier(),
eventId = eventTimelineItem.eventId()?.let { EventId(it) },
isEditable = eventTimelineItem.isEditable(),
isLocal = eventTimelineItem.isLocal(),
isOwn = eventTimelineItem.isOwn(),
isRemote = eventTimelineItem.isRemote(),
localSendState = eventTimelineItem.localSendState()?.map(),
reactions = eventTimelineItem.reactions().map(),
sender = UserId(eventTimelineItem.sender()),
senderProfile = eventTimelineItem.senderProfile().map(),
timestamp = eventTimelineItem.timestamp().toLong(),
content = contentMapper.map(eventTimelineItem.content())
)
}
}
fun RustProfileTimelineDetails.map(): ProfileTimelineDetails {
return when (this) {
RustProfileTimelineDetails.Pending -> ProfileTimelineDetails.Pending
RustProfileTimelineDetails.Unavailable -> ProfileTimelineDetails.Unavailable
is RustProfileTimelineDetails.Error -> ProfileTimelineDetails.Error(message)
is RustProfileTimelineDetails.Ready -> ProfileTimelineDetails.Ready(
displayName = displayName,
displayNameAmbiguous = displayNameAmbiguous,
avatarUrl = avatarUrl
)
}
}
fun RustEventSendState?.map(): EventSendState? {
return when (this) {
null -> null
RustEventSendState.NotSendYet -> EventSendState.NotSendYet
is RustEventSendState.SendingFailed -> EventSendState.SendingFailed(error)
is RustEventSendState.Sent -> EventSendState.Sent(EventId(eventId))
}
}
private fun List<Reaction>?.map(): List<EventReaction> {
return this?.map {
EventReaction(
key = it.key,
count = it.count.toLong()
)
} ?: emptyList()
}

View file

@ -0,0 +1,158 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.timeline.item.event
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
import io.element.android.libraries.matrix.api.timeline.item.event.OtherState
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
import io.element.android.libraries.matrix.impl.media.map
import org.matrix.rustcomponents.sdk.TimelineItemContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
import org.matrix.rustcomponents.sdk.EncryptedMessage as RustEncryptedMessage
import org.matrix.rustcomponents.sdk.MembershipChange as RustMembershipChange
import org.matrix.rustcomponents.sdk.OtherState as RustOtherState
class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMapper = EventMessageMapper()) {
fun map(content: TimelineItemContent): EventContent = content.use {
when (val kind = content.kind()) {
is TimelineItemContentKind.FailedToParseMessageLike -> {
FailedToParseMessageLikeContent(
eventType = kind.eventType,
error = kind.error
)
}
is TimelineItemContentKind.FailedToParseState -> {
FailedToParseStateContent(
eventType = kind.eventType,
stateKey = kind.stateKey,
error = kind.error
)
}
TimelineItemContentKind.Message -> {
val message = content.asMessage()
if (message == null) {
UnknownContent
} else {
eventMessageMapper.map(message)
}
}
is TimelineItemContentKind.ProfileChange -> {
ProfileChangeContent(
displayName = kind.displayName,
prevDisplayName = kind.prevDisplayName,
avatarUrl = kind.avatarUrl,
prevAvatarUrl = kind.prevAvatarUrl
)
}
TimelineItemContentKind.RedactedMessage -> {
RedactedContent
}
is TimelineItemContentKind.RoomMembership -> {
RoomMembershipContent(
UserId(kind.userId),
kind.change?.map()
)
}
is TimelineItemContentKind.State -> {
StateContent(
stateKey = kind.stateKey,
content = kind.content.map()
)
}
is TimelineItemContentKind.Sticker -> {
StickerContent(
body = kind.body,
info = kind.info.map(),
url = kind.url
)
}
is TimelineItemContentKind.UnableToDecrypt -> {
UnableToDecryptContent(
data = kind.msg.map()
)
}
}
}
}
private fun RustMembershipChange.map(): MembershipChange {
return when (this) {
RustMembershipChange.NONE -> MembershipChange.NONE
RustMembershipChange.ERROR -> MembershipChange.ERROR
RustMembershipChange.JOINED -> MembershipChange.JOINED
RustMembershipChange.LEFT -> MembershipChange.LEFT
RustMembershipChange.BANNED -> MembershipChange.BANNED
RustMembershipChange.UNBANNED -> MembershipChange.UNBANNED
RustMembershipChange.KICKED -> MembershipChange.KICKED
RustMembershipChange.INVITED -> MembershipChange.INVITED
RustMembershipChange.KICKED_AND_BANNED -> MembershipChange.KICKED_AND_BANNED
RustMembershipChange.INVITATION_ACCEPTED -> MembershipChange.INVITATION_ACCEPTED
RustMembershipChange.INVITATION_REJECTED -> MembershipChange.INVITATION_REJECTED
RustMembershipChange.INVITATION_REVOKED -> MembershipChange.INVITATION_REVOKED
RustMembershipChange.KNOCKED -> MembershipChange.KNOCKED
RustMembershipChange.KNOCK_ACCEPTED -> MembershipChange.KNOCK_ACCEPTED
RustMembershipChange.KNOCK_RETRACTED -> MembershipChange.KNOCK_RETRACTED
RustMembershipChange.KNOCK_DENIED -> MembershipChange.KNOCK_DENIED
RustMembershipChange.NOT_IMPLEMENTED -> MembershipChange.NOT_IMPLEMENTED
}
}
//TODO extract state events?
private fun RustOtherState.map(): OtherState {
return when (this) {
is RustOtherState.Custom -> OtherState.Custom(eventType)
RustOtherState.PolicyRuleRoom -> OtherState.PolicyRuleRoom
RustOtherState.PolicyRuleServer -> OtherState.PolicyRuleServer
RustOtherState.PolicyRuleUser -> OtherState.PolicyRuleUser
RustOtherState.RoomAliases -> OtherState.RoomAliases
is RustOtherState.RoomAvatar -> OtherState.RoomAvatar(url)
RustOtherState.RoomCanonicalAlias -> OtherState.RoomCanonicalAlias
RustOtherState.RoomCreate -> OtherState.RoomCreate
RustOtherState.RoomEncryption -> OtherState.RoomEncryption
RustOtherState.RoomGuestAccess -> OtherState.RoomGuestAccess
RustOtherState.RoomHistoryVisibility -> OtherState.RoomHistoryVisibility
RustOtherState.RoomJoinRules -> OtherState.RoomJoinRules
is RustOtherState.RoomName -> OtherState.RoomName(name)
RustOtherState.RoomPinnedEvents -> OtherState.RoomPinnedEvents
RustOtherState.RoomPowerLevels -> OtherState.RoomPowerLevels
RustOtherState.RoomServerAcl -> OtherState.RoomServerAcl
is RustOtherState.RoomThirdPartyInvite -> OtherState.RoomThirdPartyInvite(displayName)
RustOtherState.RoomTombstone -> OtherState.RoomTombstone
is RustOtherState.RoomTopic -> OtherState.RoomTopic(topic)
RustOtherState.SpaceChild -> OtherState.SpaceChild
RustOtherState.SpaceParent -> OtherState.SpaceParent
}
}
private fun RustEncryptedMessage.map(): UnableToDecryptContent.Data {
return when (this) {
is RustEncryptedMessage.MegolmV1AesSha2 -> UnableToDecryptContent.Data.MegolmV1AesSha2(sessionId)
is RustEncryptedMessage.OlmV1Curve25519AesSha2 -> UnableToDecryptContent.Data.OlmV1Curve25519AesSha2(senderKey)
RustEncryptedMessage.Unknown -> UnableToDecryptContent.Data.Unknown
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.timeline.item.virtual
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
import org.matrix.rustcomponents.sdk.VirtualTimelineItem as RustVirtualTimelineItem
class VirtualTimelineItemMapper {
fun map(virtualTimelineItem: RustVirtualTimelineItem): VirtualTimelineItem {
return when (virtualTimelineItem) {
is RustVirtualTimelineItem.DayDivider -> VirtualTimelineItem.DayDivider(virtualTimelineItem.ts.toLong())
RustVirtualTimelineItem.LoadingIndicator -> VirtualTimelineItem.LoadingIndicator
RustVirtualTimelineItem.ReadMarker -> VirtualTimelineItem.ReadMarker
RustVirtualTimelineItem.TimelineStart -> VirtualTimelineItem.TimelineStart
}
}
}

View file

@ -27,7 +27,10 @@ class TaskHandleBag(private val tokens: MutableSet<TaskHandle> = CopyOnWriteArra
} }
fun dispose() { fun dispose() {
tokens.forEach { it.cancel() } tokens.forEach {
it.cancel()
it.destroy()
}
tokens.clear() tokens.clear()
} }
} }

View file

@ -26,13 +26,12 @@ import io.element.android.libraries.matrix.test.media.FakeMediaResolver
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import org.matrix.rustcomponents.sdk.MediaSource
class FakeMatrixClient( class FakeMatrixClient(
override val sessionId: SessionId = A_SESSION_ID, override val sessionId: SessionId = A_SESSION_ID,
private val userDisplayName: Result<String> = Result.success(A_USER_NAME), private val userDisplayName: Result<String> = Result.success(A_USER_NAME),
private val userAvatarURLString: Result<String> = Result.success(AN_AVATAR_URL), private val userAvatarURLString: Result<String> = Result.success(AN_AVATAR_URL),
val roomSummaryDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource() override val roomSummaryDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource()
) : MatrixClient { ) : MatrixClient {
private var logoutFailure: Throwable? = null private var logoutFailure: Throwable? = null
@ -45,10 +44,6 @@ class FakeMatrixClient(
override fun stopSync() = Unit override fun stopSync() = Unit
override fun roomSummaryDataSource(): RoomSummaryDataSource {
return roomSummaryDataSource
}
override fun mediaResolver(): MediaResolver { override fun mediaResolver(): MediaResolver {
return FakeMediaResolver() return FakeMediaResolver()
} }
@ -70,13 +65,11 @@ class FakeMatrixClient(
return userAvatarURLString return userAvatarURLString
} }
override suspend fun loadMediaContentForSource(source: MediaSource): Result<ByteArray> { override suspend fun loadMediaContent(url: String): Result<ByteArray> {
return Result.success(ByteArray(0)) return Result.success(ByteArray(0))
} }
override suspend fun loadMediaThumbnailForSource(source: MediaSource, width: Long, height: Long): Result<ByteArray> { override suspend fun loadMediaThumbnail(url: String, width: Long, height: Long): Result<ByteArray> {
return Result.success(ByteArray(0)) return Result.success(ByteArray(0))
} }
override fun close() = Unit
} }

View file

@ -20,7 +20,6 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.test.A_HOMESERVER
import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -41,8 +40,8 @@ class FakeAuthenticationService : MatrixAuthenticationService {
return null return null
} }
override suspend fun restoreSession(sessionId: SessionId): MatrixClient? { override suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient> {
return null return Result.failure(IllegalStateException())
} }
override fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?> { override fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?> {
@ -53,15 +52,21 @@ class FakeAuthenticationService : MatrixAuthenticationService {
this.homeserver.value = homeserver this.homeserver.value = homeserver
} }
override suspend fun setHomeserver(homeserver: String) { override suspend fun setHomeserver(homeserver: String): Result<Unit> {
changeServerError?.let { throw it } changeServerError?.let { throw it }
delay(100) delay(100)
return Result.success(Unit)
} }
override suspend fun login(username: String, password: String): SessionId { override suspend fun login(username: String, password: String): Result<SessionId> {
delay(100) delay(100)
loginError?.let { throw it } return loginError.let { loginError ->
return A_USER_ID if (loginError == null) {
Result.success(A_USER_ID)
} else {
Result.failure(loginError)
}
}
} }
fun givenLoginError(throwable: Throwable?) { fun givenLoginError(throwable: Throwable?) {

View file

@ -22,8 +22,4 @@ class FakeMediaResolver : MediaResolver {
override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? { override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? {
return null return null
} }
override suspend fun resolve(meta: MediaResolver.Meta): ByteArray? {
return null
}
} }

View file

@ -87,4 +87,6 @@ class FakeMatrixRoom(
delay(100) delay(100)
return Result.success(Unit) return Result.success(Unit)
} }
override fun close() = Unit
} }

View file

@ -18,9 +18,7 @@ package io.element.android.libraries.matrix.ui.media
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.media.MediaResolver import io.element.android.libraries.matrix.api.media.MediaResolver
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
fun AvatarData.toMetadata(): MediaResolver.Meta { fun AvatarData.toMetadata(): MediaResolver.Meta {
val mediaSource = url?.let { mediaSourceFromUrl(it) } return MediaResolver.Meta(url = url, kind = MediaResolver.Kind.Thumbnail(size.value))
return MediaResolver.Meta(source = mediaSource, kind = MediaResolver.Kind.Thumbnail(size.value))
} }

View file

@ -33,7 +33,7 @@ internal class MediaFetcher(
) : Fetcher { ) : Fetcher {
override suspend fun fetch(): FetchResult? { override suspend fun fetch(): FetchResult? {
val byteArray = mediaResolver?.resolve(meta) ?: return null val byteArray = mediaResolver?.resolve(meta.url, meta.kind) ?: return null
val byteBuffer = ByteBuffer.wrap(byteArray) val byteBuffer = ByteBuffer.wrap(byteArray)
return imageLoader.components.newFetcher(byteBuffer, options, imageLoader)?.first?.fetch() return imageLoader.components.newFetcher(byteBuffer, options, imageLoader)?.first?.fetch()
} }

View file

@ -33,4 +33,4 @@ internal class MediaKeyer : Keyer<MediaResolver.Meta> {
} }
} }
private fun MediaResolver.Meta.toKey() = "${source?.url()}_${kind}" private fun MediaResolver.Meta.toKey() = "${url}_${kind}"

View file

@ -33,7 +33,6 @@ import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.impl.auth.RustMatrixAuthenticationService import io.element.android.libraries.matrix.impl.auth.RustMatrixAuthenticationService
import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.matrix.rustcomponents.sdk.AuthenticationService
import java.io.File import java.io.File
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@ -46,7 +45,6 @@ class MainActivity : ComponentActivity() {
coroutineScope = Singleton.appScope, coroutineScope = Singleton.appScope,
coroutineDispatchers = Singleton.coroutineDispatchers, coroutineDispatchers = Singleton.coroutineDispatchers,
sessionStore = InMemorySessionStore(), sessionStore = InMemorySessionStore(),
authService = AuthenticationService(baseDirectory.absolutePath, null, null),
) )
} }
@ -72,7 +70,7 @@ class MainActivity : ComponentActivity() {
} else { } else {
val matrixClient = runBlocking { val matrixClient = runBlocking {
val sessionId = matrixAuthenticationService.getLatestSessionId()!! val sessionId = matrixAuthenticationService.getLatestSessionId()!!
matrixAuthenticationService.restoreSession(sessionId) matrixAuthenticationService.restoreSession(sessionId).getOrNull()
} }
RoomListScreen(matrixClient = matrixClient!!).Content(modifier) RoomListScreen(matrixClient = matrixClient!!).Content(modifier)
} }