[MatrixSDK] finish mapping timeline and makes it compile

This commit is contained in:
ganfra 2023-03-13 20:18:16 +01:00
parent 8ccd21d856
commit 38dc36e7bf
44 changed files with 370 additions and 242 deletions

View file

@ -219,6 +219,7 @@ dependencies {
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.startup) implementation(libs.androidx.startup)
implementation(libs.coil) implementation(libs.coil)
implementation(libs.matrix.sdk)
implementation(libs.dagger) implementation(libs.dagger)
kapt(libs.dagger.compiler) kapt(libs.dagger.compiler)

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

@ -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.trim())
} .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

@ -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.TimelineEventContent
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: TimelineEventContent): 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(
url = 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

@ -23,7 +23,6 @@ import io.element.android.features.messages.impl.timeline.model.virtual.Timeline
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 io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
import org.matrix.rustcomponents.sdk.VirtualTimelineItem
import javax.inject.Inject import javax.inject.Inject
class TimelineItemVirtualFactory @Inject constructor( class TimelineItemVirtualFactory @Inject constructor(
@ -43,10 +42,10 @@ class TimelineItemVirtualFactory @Inject constructor(
private fun MatrixTimelineItem.Virtual.computeModel(index: Int): TimelineItemVirtualModel { private fun MatrixTimelineItem.Virtual.computeModel(index: Int): TimelineItemVirtualModel {
return when (val inner = virtual) { return when (val inner = virtual) {
is io.element.android.libraries.matrix.api.timeline.item.virtual.TimelineItemVirtual.VirtualTimelineItem.DayDivider -> daySeparatorFactory.create(inner) is VirtualTimelineItem.DayDivider -> daySeparatorFactory.create(inner)
is io.element.android.libraries.matrix.api.timeline.item.virtual.TimelineItemVirtual.VirtualTimelineItem.ReadMarker -> TimelineItemReadMarkerModel is VirtualTimelineItem.ReadMarker -> TimelineItemReadMarkerModel
is io.element.android.libraries.matrix.api.timeline.item.virtual.TimelineItemVirtual.VirtualTimelineItem.LoadingIndicator -> TimelineItemLoadingModel is VirtualTimelineItem.LoadingIndicator -> TimelineItemLoadingModel
is io.element.android.libraries.matrix.api.timeline.item.virtual.TimelineItemVirtual.VirtualTimelineItem.TimelineStart -> TimelineItemReadMarkerModel is VirtualTimelineItem.TimelineStart -> TimelineItemReadMarkerModel
else -> TimelineItemUnknownVirtualModel else -> TimelineItemUnknownVirtualModel
} }
} }

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

@ -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

@ -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.core.extensions
/**
* Can be used to transform some Throwable into some other
*/
inline fun <R, T : R> Result<T>.mapFailure(transform: (exception: Throwable) -> Throwable): Result<R> {
return when (val exception = exceptionOrNull()) {
null -> this
else -> Result.failure(transform(exception))
}
}

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,8 +21,6 @@ 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 { interface MatrixClient {
val sessionId: SessionId val sessionId: SessionId
@ -34,9 +32,9 @@ interface MatrixClient {
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

@ -24,11 +24,11 @@ import io.element.android.libraries.matrix.api.media.VideoInfo
sealed interface TimelineEventContent sealed interface TimelineEventContent
data class TimelineEventMessageContent( data class MessageContent(
val body: String, val body: String,
val inReplyTo: UserId?, val inReplyTo: UserId?,
val isEdited: Boolean, val isEdited: Boolean,
val content: MessageContent? val type: MessageType?
) : TimelineEventContent ) : TimelineEventContent
object RedactedContent : TimelineEventContent object RedactedContent : TimelineEventContent
@ -39,45 +39,45 @@ data class StickerContent(
val url: String val url: String
) : TimelineEventContent ) : TimelineEventContent
sealed interface EncryptedMessage { data class UnableToDecryptContent(
data class OlmV1Curve25519AesSha2( val data: Data
val senderKey: String ) : TimelineEventContent {
) : EncryptedMessage sealed interface Data {
data class OlmV1Curve25519AesSha2(
val senderKey: String
) : Data
data class MegolmV1AesSha2( data class MegolmV1AesSha2(
val sessionId: String val sessionId: String
) : EncryptedMessage ) : Data
object Unknown : EncryptedMessage object Unknown : Data
}
} }
data class UnableToDecryptContent( data class RoomMembershipContent(
val message: EncryptedMessage
) : TimelineEventContent
data class RoomMembership(
val userId: UserId, val userId: UserId,
val change: MembershipChange? val change: MembershipChange?
) : TimelineEventContent ) : TimelineEventContent
data class ProfileChange( data class ProfileChangeContent(
val displayName: String?, val displayName: String?,
val prevDisplayName: String?, val prevDisplayName: String?,
val avatarUrl: String?, val avatarUrl: String?,
val prevAvatarUrl: String? val prevAvatarUrl: String?
) : TimelineEventContent ) : TimelineEventContent
data class State( data class StateContent(
val stateKey: String, val stateKey: String,
val content: OtherState val content: OtherState
) : TimelineEventContent ) : TimelineEventContent
data class FailedToParseMessageLike( data class FailedToParseMessageLikeContent(
val eventType: String, val eventType: String,
val error: String val error: String
) : TimelineEventContent ) : TimelineEventContent
data class FailedToParseState( data class FailedToParseStateContent(
val eventType: String, val eventType: String,
val stateKey: String, val stateKey: String,
val error: String val error: String
@ -85,9 +85,9 @@ data class FailedToParseState(
object UnknownContent : TimelineEventContent object UnknownContent : TimelineEventContent
sealed interface MessageContent sealed interface MessageType
object UnknownMessageContent : MessageContent object UnknownMessageType : MessageType
enum class MessageFormat { enum class MessageFormat {
HTML, UNKNOWN HTML, UNKNOWN
@ -98,44 +98,44 @@ data class FormattedBody(
val body: String val body: String
) )
data class EmoteMessageContent( data class EmoteMessageType(
val body: String, val body: String,
val formatted: FormattedBody? val formatted: FormattedBody?
) : MessageContent ) : MessageType
data class ImageMessageContent( data class ImageMessageType(
val body: String, val body: String,
val url: String, val url: String,
val info: ImageInfo? val info: ImageInfo?
) : MessageContent ) : MessageType
data class AudioMessageContent( data class AudioMessageType(
var body: String, var body: String,
var url: String, var url: String,
var info: AudioInfo? var info: AudioInfo?
) : MessageContent ) : MessageType
data class VideoMessageContent( data class VideoMessageType(
val body: String, val body: String,
val url: String, val url: String,
val info: VideoInfo? val info: VideoInfo?
) : MessageContent ) : MessageType
data class FileMessageContent( data class FileMessageType(
val body: String, val body: String,
val url: String, val url: String,
val info: FileInfo? val info: FileInfo?
) : MessageContent ) : MessageType
data class NoticeMessageContent( data class NoticeMessageType(
val body: String, val body: String,
val formatted: FormattedBody? val formatted: FormattedBody?
) : MessageContent ) : MessageType
data class TextMessageContent( data class TextMessageType(
val body: String, val body: String,
val formatted: FormattedBody? val formatted: FormattedBody?
) : MessageContent ) : MessageType
enum class MembershipChange { enum class MembershipChange {
NONE, NONE,

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

@ -32,12 +32,12 @@ 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 org.matrix.rustcomponents.sdk.use
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
@ -181,9 +181,9 @@ class RustMatrixClient constructor(
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.e(failure, "Fail to call logout on HS. Still delete local files.") Timber.e(failure, "Fail to call logout on HS. Still delete local files.")
} }
client.destroy()
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) {
@ -199,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
@ -39,7 +39,6 @@ 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
@ -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

@ -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.MediaSource
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
return mediaSourceFromUrl(url).use { mediaSource ->
resolve(mediaSource, kind)
}
}
private suspend fun resolve(mediaSource: MediaSource, kind: MediaResolver.Kind): ByteArray? {
return when (kind) { return when (kind) {
is MediaResolver.Kind.Content -> client.loadMediaContentForSource(mediaSource) is MediaResolver.Kind.Content -> client.loadMediaContent(url)
is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnailForSource( is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnail(
mediaSource, url,
kind.width.toLong(), kind.width.toLong(),
kind.height.toLong() kind.height.toLong()
) )

View file

@ -17,17 +17,17 @@
package io.element.android.libraries.matrix.impl.timeline.item.event 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.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageContent import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageContent import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageContent 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.FormattedBody
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageContent 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.MessageFormat
import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageContent import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageContent import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineEventMessageContent import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageContent import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageContent 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.map
import io.element.android.libraries.matrix.impl.media.useUrl import io.element.android.libraries.matrix.impl.media.useUrl
import org.matrix.rustcomponents.sdk.Message import org.matrix.rustcomponents.sdk.Message
@ -38,40 +38,40 @@ import org.matrix.rustcomponents.sdk.MessageFormat as RustMessageFormat
class EventMessageMapper { class EventMessageMapper {
fun map(message: Message): TimelineEventMessageContent = message.use { fun map(message: Message): MessageContent = message.use {
val content = message.msgtype().use { type -> val content = message.msgtype().use { type ->
when (type) { when (type) {
is MessageType.Audio -> { is MessageType.Audio -> {
AudioMessageContent(type.content.body, type.content.source.useUrl(), type.content.info?.map()) AudioMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map())
} }
is MessageType.File -> { is MessageType.File -> {
FileMessageContent(type.content.body, type.content.source.useUrl(), type.content.info?.map()) FileMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map())
} }
is MessageType.Image -> { is MessageType.Image -> {
ImageMessageContent(type.content.body, type.content.source.useUrl(), type.content.info?.map()) ImageMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map())
} }
is MessageType.Notice -> { is MessageType.Notice -> {
NoticeMessageContent(type.content.body, type.content.formatted?.map()) NoticeMessageType(type.content.body, type.content.formatted?.map())
} }
is MessageType.Text -> { is MessageType.Text -> {
TextMessageContent(type.content.body, type.content.formatted?.map()) TextMessageType(type.content.body, type.content.formatted?.map())
} }
is MessageType.Emote -> { is MessageType.Emote -> {
EmoteMessageContent(type.content.body, type.content.formatted?.map()) EmoteMessageType(type.content.body, type.content.formatted?.map())
} }
is MessageType.Video -> { is MessageType.Video -> {
VideoMessageContent(type.content.body, type.content.source.useUrl(), type.content.info?.map()) VideoMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map())
} }
null -> { null -> {
UnknownMessageContent UnknownMessageType
} }
} }
} }
TimelineEventMessageContent( MessageContent(
body = message.body(), body = message.body(),
inReplyTo = message.inReplyTo()?.let { UserId(it) }, inReplyTo = message.inReplyTo()?.let { UserId(it) },
isEdited = message.isEdited(), isEdited = message.isEdited(),
content = content type = content
) )
} }
} }

View file

@ -17,31 +17,33 @@
package io.element.android.libraries.matrix.impl.timeline.item.event 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.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLike import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseState 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.MembershipChange
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChange 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.RedactedContent
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembership import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineEventContent import io.element.android.libraries.matrix.api.timeline.item.event.TimelineEventContent
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.api.timeline.item.event.UnknownContent
import io.element.android.libraries.matrix.impl.media.map import io.element.android.libraries.matrix.impl.media.map
import org.matrix.rustcomponents.sdk.TimelineItemContent import org.matrix.rustcomponents.sdk.TimelineItemContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind import org.matrix.rustcomponents.sdk.TimelineItemContentKind
import org.matrix.rustcomponents.sdk.EncryptedMessage as RustEncryptedMessage
class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMapper = EventMessageMapper()) { class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMapper = EventMessageMapper()) {
fun map(content: TimelineItemContent): TimelineEventContent = content.use { fun map(content: TimelineItemContent): TimelineEventContent = content.use {
when (val kind = content.kind()) { when (val kind = content.kind()) {
is TimelineItemContentKind.FailedToParseMessageLike -> { is TimelineItemContentKind.FailedToParseMessageLike -> {
FailedToParseMessageLike( FailedToParseMessageLikeContent(
eventType = kind.eventType, eventType = kind.eventType,
error = kind.error error = kind.error
) )
} }
is TimelineItemContentKind.FailedToParseState -> { is TimelineItemContentKind.FailedToParseState -> {
FailedToParseState( FailedToParseStateContent(
eventType = kind.eventType, eventType = kind.eventType,
stateKey = kind.stateKey, stateKey = kind.stateKey,
error = kind.error error = kind.error
@ -56,7 +58,7 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap
} }
} }
is TimelineItemContentKind.ProfileChange -> { is TimelineItemContentKind.ProfileChange -> {
ProfileChange( ProfileChangeContent(
displayName = kind.displayName, displayName = kind.displayName,
prevDisplayName = kind.prevDisplayName, prevDisplayName = kind.prevDisplayName,
avatarUrl = kind.avatarUrl, avatarUrl = kind.avatarUrl,
@ -67,7 +69,7 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap
RedactedContent RedactedContent
} }
is TimelineItemContentKind.RoomMembership -> { is TimelineItemContentKind.RoomMembership -> {
RoomMembership( RoomMembershipContent(
UserId(kind.userId), UserId(kind.userId),
MembershipChange.JOINED MembershipChange.JOINED
) )
@ -83,8 +85,18 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap
) )
} }
is TimelineItemContentKind.UnableToDecrypt -> { is TimelineItemContentKind.UnableToDecrypt -> {
UnknownContent UnableToDecryptContent(
data = kind.msg.map()
)
} }
} }
} }
} }
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

@ -26,7 +26,6 @@ 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,
@ -66,11 +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))
} }
} }

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,16 @@ 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 } loginError?.let { throw it }
return A_USER_ID return Result.success(A_USER_ID)
} }
fun givenLoginError(throwable: Throwable?) { fun givenLoginError(throwable: Throwable?) {

View file

@ -57,5 +57,6 @@ dependencies {
implementation(projects.features.roomlist.impl) implementation(projects.features.roomlist.impl)
implementation(projects.features.login.impl) implementation(projects.features.login.impl)
implementation(libs.coroutines.core) implementation(libs.coroutines.core)
implementation(libs.matrix.sdk)
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.2") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.2")
} }

View file

@ -72,7 +72,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)
} }