Merge pull request #213 from vector-im/feature/fga/hide_rust_sdk
Feature/fga/hide rust sdk
This commit is contained in:
@@ -71,6 +71,7 @@ class RoomFlowNode @AssistedInject constructor(
|
||||
},
|
||||
onDestroy = {
|
||||
Timber.v("OnDestroy")
|
||||
inputs.room.close()
|
||||
plugins<LifecycleCallback>().forEach { it.onFlowReleased(inputs.room) }
|
||||
}
|
||||
)
|
||||
|
||||
@@ -119,14 +119,15 @@ class RootFlowNode @AssistedInject constructor(
|
||||
onSuccess(latestKnownUserId)
|
||||
return
|
||||
}
|
||||
val matrixClient = authenticationService.restoreSession(UserId(latestKnownUserId.value))
|
||||
if (matrixClient == null) {
|
||||
Timber.v("Failed to restore session...")
|
||||
onFailure()
|
||||
} else {
|
||||
matrixClientsHolder.add(matrixClient)
|
||||
onSuccess(matrixClient.sessionId)
|
||||
}
|
||||
authenticationService.restoreSession(UserId(latestKnownUserId.value))
|
||||
.onSuccess { matrixClient ->
|
||||
matrixClientsHolder.add(matrixClient)
|
||||
onSuccess(matrixClient.sessionId)
|
||||
}
|
||||
.onFailure {
|
||||
Timber.v("Failed to restore session...")
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onOpenBugReport() {
|
||||
|
||||
@@ -64,10 +64,13 @@ class MatrixClientsHolder @Inject constructor(private val authenticationService:
|
||||
runBlocking {
|
||||
userIds.forEach { userId ->
|
||||
Timber.v("Restore matrix session: $userId")
|
||||
val matrixClient = authenticationService.restoreSession(userId)
|
||||
if (matrixClient != null) {
|
||||
add(matrixClient)
|
||||
}
|
||||
authenticationService.restoreSession(userId)
|
||||
.onSuccess { matrixClient ->
|
||||
add(matrixClient)
|
||||
}
|
||||
.onFailure {
|
||||
Timber.e("Fail to restore session")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,10 +214,10 @@ koverMerged {
|
||||
name = "Global minimum code coverage."
|
||||
target = kotlinx.kover.api.VerificationTarget.ALL
|
||||
bound {
|
||||
minValue = 55
|
||||
minValue = 50
|
||||
// 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
|
||||
// minValue to 35 and maxValue to 40.
|
||||
// For instance if we have minValue = 20 and maxValue = 30, and current code coverage is now 31.32%, update
|
||||
// minValue to 25 and maxValue to 35.
|
||||
maxValue = 60
|
||||
counter = kotlinx.kover.api.CounterType.INSTRUCTION
|
||||
valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE
|
||||
@@ -243,6 +243,8 @@ koverMerged {
|
||||
target = kotlinx.kover.api.VerificationTarget.CLASS
|
||||
overrideClassFilter {
|
||||
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 {
|
||||
minValue = 90
|
||||
|
||||
@@ -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.TopAppBar
|
||||
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.testTag
|
||||
import org.matrix.rustcomponents.sdk.AuthenticationException
|
||||
import io.element.android.libraries.ui.strings.R as StringR
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalTextApi::class)
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
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.AuthenticationException
|
||||
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
|
||||
|
||||
fun loginError(
|
||||
|
||||
@@ -25,7 +25,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import io.element.android.features.login.impl.util.LoginConstants
|
||||
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.MatrixHomeServerDetails
|
||||
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 {
|
||||
loggedInState.value = LoggedInState.LoggingIn
|
||||
//TODO rework the setHomeserver flow
|
||||
tryOrNull {
|
||||
authenticationService.setHomeserver(homeserver)
|
||||
}
|
||||
try {
|
||||
val sessionId = authenticationService.login(formState.login.trim(), formState.password.trim())
|
||||
loggedInState.value = LoggedInState.LoggedIn(sessionId)
|
||||
} catch (failure: Throwable) {
|
||||
loggedInState.value = LoggedInState.ErrorLoggingIn(failure)
|
||||
}
|
||||
authenticationService.setHomeserver(homeserver)
|
||||
authenticationService.login(formState.login.trim(), formState.password)
|
||||
.onSuccess { sessionId ->
|
||||
loggedInState.value = LoggedInState.LoggedIn(sessionId)
|
||||
}
|
||||
.onFailure { failure ->
|
||||
loggedInState.value = LoggedInState.ErrorLoggingIn(failure)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateFormState(formState: MutableState<LoginFormState>, updateLambda: LoginFormState.() -> LoginFormState) {
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
package io.element.android.features.login.impl.error
|
||||
|
||||
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 org.junit.Test
|
||||
import org.matrix.rustcomponents.sdk.AuthenticationException
|
||||
|
||||
class ErrorFormatterTests {
|
||||
|
||||
|
||||
@@ -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.libraries.designsystem.components.avatar.AvatarData
|
||||
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 kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -81,7 +82,7 @@ internal fun aTimelineItemEvent(
|
||||
return TimelineItem.Event(
|
||||
id = randomId,
|
||||
eventId = EventId(randomId),
|
||||
senderId = "@senderId",
|
||||
senderId = UserId("@senderId"),
|
||||
senderAvatar = AvatarData("@senderId", "sender"),
|
||||
content = content,
|
||||
reactionsState = TimelineItemReactions(
|
||||
|
||||
@@ -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.libraries.designsystem.preview.ElementPreviewDark
|
||||
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
|
||||
fun TimelineItemEncryptedView(
|
||||
@@ -53,7 +53,7 @@ internal fun TimelineItemEncryptedViewDarkPreview() =
|
||||
private fun ContentToPreview() {
|
||||
TimelineItemEncryptedView(
|
||||
content = TimelineItemEncryptedContent(
|
||||
encryptedMessage = EncryptedMessage.Unknown,
|
||||
data = UnableToDecryptContent.Data.Unknown
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,11 +17,20 @@
|
||||
package io.element.android.features.messages.impl.timeline.factories.event
|
||||
|
||||
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
|
||||
|
||||
typealias RustTimelineItemContent = org.matrix.rustcomponents.sdk.TimelineItemContent
|
||||
|
||||
class TimelineItemContentFactory @Inject constructor(
|
||||
private val messageFactory: TimelineItemContentMessageFactory,
|
||||
private val redactedMessageFactory: TimelineItemContentRedactedFactory,
|
||||
@@ -34,17 +43,18 @@ class TimelineItemContentFactory @Inject constructor(
|
||||
private val failedToParseStateFactory: TimelineItemContentFailedToParseStateFactory
|
||||
) {
|
||||
|
||||
fun create(itemContent: RustTimelineItemContent): TimelineItemEventContent {
|
||||
return when (val kind = itemContent.kind()) {
|
||||
is TimelineItemContentKind.Message -> messageFactory.create(itemContent.asMessage())
|
||||
is TimelineItemContentKind.RedactedMessage -> redactedMessageFactory.create(kind)
|
||||
is TimelineItemContentKind.Sticker -> stickerFactory.create(kind)
|
||||
is TimelineItemContentKind.UnableToDecrypt -> utdFactory.create(kind)
|
||||
is TimelineItemContentKind.RoomMembership -> roomMembershipFactory.create(kind)
|
||||
is TimelineItemContentKind.ProfileChange -> profileChangeFactory.create(kind)
|
||||
is TimelineItemContentKind.State -> stateFactory.create(kind)
|
||||
is TimelineItemContentKind.FailedToParseMessageLike -> failedToParseMessageFactory.create(kind)
|
||||
is TimelineItemContentKind.FailedToParseState -> failedToParseStateFactory.create(kind)
|
||||
fun create(itemContent: EventContent): TimelineItemEventContent {
|
||||
return when (itemContent) {
|
||||
is FailedToParseMessageLikeContent -> failedToParseMessageFactory.create(itemContent)
|
||||
is FailedToParseStateContent -> failedToParseStateFactory.create(itemContent)
|
||||
is MessageContent -> messageFactory.create(itemContent)
|
||||
is ProfileChangeContent -> profileChangeFactory.create(itemContent)
|
||||
is RedactedContent -> redactedMessageFactory.create(itemContent)
|
||||
is RoomMembershipContent -> roomMembershipFactory.create(itemContent)
|
||||
is StateContent -> stateFactory.create(itemContent)
|
||||
is StickerContent -> stickerFactory.create(itemContent)
|
||||
is UnableToDecryptContent -> utdFactory.create(itemContent)
|
||||
is UnknownContent -> TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.TimelineItemUnknownContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentFailedToParseMessageFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.FailedToParseMessageLike): TimelineItemEventContent {
|
||||
fun create(failedToParseMessageLike: FailedToParseMessageLikeContent): TimelineItemEventContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.TimelineItemUnknownContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentFailedToParseStateFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.FailedToParseState): TimelineItemEventContent {
|
||||
fun create(failedToParseState: FailedToParseStateContent): TimelineItemEventContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.util.toHtmlDocument
|
||||
import io.element.android.libraries.matrix.api.media.MediaResolver
|
||||
import org.matrix.rustcomponents.sdk.Message
|
||||
import org.matrix.rustcomponents.sdk.MessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
|
||||
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
|
||||
|
||||
class TimelineItemContentMessageFactory @Inject constructor() {
|
||||
|
||||
fun create(contentAsMessage: Message?): TimelineItemEventContent {
|
||||
return when (val messageType = contentAsMessage?.msgtype()) {
|
||||
is MessageType.Emote -> TimelineItemEmoteContent(
|
||||
body = messageType.content.body,
|
||||
htmlDocument = messageType.content.formatted?.toHtmlDocument()
|
||||
fun create(content: MessageContent): TimelineItemEventContent {
|
||||
return when (val messageType = content.type) {
|
||||
is EmoteMessageType -> TimelineItemEmoteContent(
|
||||
body = messageType.body,
|
||||
htmlDocument = messageType.formatted?.toHtmlDocument()
|
||||
)
|
||||
is MessageType.Image -> {
|
||||
val height = messageType.content.info?.height?.toFloat()
|
||||
val width = messageType.content.info?.width?.toFloat()
|
||||
is ImageMessageType -> {
|
||||
val height = messageType.info?.height?.toFloat()
|
||||
val width = messageType.info?.width?.toFloat()
|
||||
val aspectRatio = if (height != null && width != null) {
|
||||
width / height
|
||||
} else {
|
||||
0.7f
|
||||
}
|
||||
TimelineItemImageContent(
|
||||
body = messageType.content.body,
|
||||
body = messageType.body,
|
||||
imageMeta = MediaResolver.Meta(
|
||||
source = messageType.content.source,
|
||||
url = messageType.url,
|
||||
kind = MediaResolver.Kind.Content
|
||||
),
|
||||
blurhash = messageType.content.info?.blurhash,
|
||||
blurhash = messageType.info?.blurhash,
|
||||
aspectRatio = aspectRatio
|
||||
)
|
||||
}
|
||||
is MessageType.Notice -> TimelineItemNoticeContent(
|
||||
body = messageType.content.body,
|
||||
htmlDocument = messageType.content.formatted?.toHtmlDocument()
|
||||
is NoticeMessageType -> TimelineItemNoticeContent(
|
||||
body = messageType.body,
|
||||
htmlDocument = messageType.formatted?.toHtmlDocument()
|
||||
)
|
||||
is MessageType.Text -> TimelineItemTextContent(
|
||||
body = messageType.content.body,
|
||||
htmlDocument = messageType.content.formatted?.toHtmlDocument()
|
||||
is TextMessageType -> TimelineItemTextContent(
|
||||
body = messageType.body,
|
||||
htmlDocument = messageType.formatted?.toHtmlDocument()
|
||||
)
|
||||
else -> TimelineItemUnknownContent
|
||||
}
|
||||
|
||||
@@ -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.TimelineItemUnknownContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentProfileChangeFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.ProfileChange): TimelineItemEventContent {
|
||||
fun create(content: ProfileChangeContent): TimelineItemEventContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.TimelineItemRedactedContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentRedactedFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.RedactedMessage): TimelineItemEventContent {
|
||||
fun create(content: RedactedContent): TimelineItemEventContent {
|
||||
return TimelineItemRedactedContent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.TimelineItemUnknownContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentRoomMembershipFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.RoomMembership): TimelineItemEventContent {
|
||||
fun create(content: RoomMembershipContent): TimelineItemEventContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.TimelineItemUnknownContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentStateFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.State): TimelineItemEventContent {
|
||||
fun create(content: StateContent): TimelineItemEventContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.TimelineItemUnknownContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentStickerFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.Sticker): TimelineItemEventContent {
|
||||
fun create(content: StickerContent): TimelineItemEventContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
|
||||
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 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
|
||||
|
||||
class TimelineItemContentUTDFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.UnableToDecrypt): TimelineItemEventContent {
|
||||
return TimelineItemEncryptedContent(kind.msg)
|
||||
fun create(content: UnableToDecryptContent): TimelineItemEventContent {
|
||||
return TimelineItemEncryptedContent(content.data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.AvatarSize
|
||||
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 org.matrix.rustcomponents.sdk.ProfileTimelineDetails
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemEventFactory @Inject constructor(
|
||||
@@ -36,13 +36,13 @@ class TimelineItemEventFactory @Inject constructor(
|
||||
index: Int,
|
||||
timelineItems: List<MatrixTimelineItem>,
|
||||
): TimelineItem.Event {
|
||||
val currentSender = currentTimelineItem.event.sender()
|
||||
val currentSender = currentTimelineItem.event.sender
|
||||
val groupPosition =
|
||||
computeGroupPosition(currentTimelineItem, timelineItems, index)
|
||||
val senderDisplayName: String?
|
||||
val senderAvatarUrl: String?
|
||||
|
||||
when (val senderProfile = currentTimelineItem.event.senderProfile()) {
|
||||
when (val senderProfile = currentTimelineItem.event.senderProfile) {
|
||||
ProfileTimelineDetails.Unavailable,
|
||||
ProfileTimelineDetails.Pending,
|
||||
is ProfileTimelineDetails.Error -> {
|
||||
@@ -56,8 +56,8 @@ class TimelineItemEventFactory @Inject constructor(
|
||||
}
|
||||
|
||||
val senderAvatarData = AvatarData(
|
||||
id = currentSender,
|
||||
name = senderDisplayName ?: currentSender,
|
||||
id = currentSender.value,
|
||||
name = senderDisplayName ?: currentSender.value,
|
||||
url = senderAvatarUrl,
|
||||
size = AvatarSize.SMALL
|
||||
)
|
||||
@@ -67,15 +67,15 @@ class TimelineItemEventFactory @Inject constructor(
|
||||
senderId = currentSender,
|
||||
senderDisplayName = senderDisplayName,
|
||||
senderAvatar = senderAvatarData,
|
||||
content = contentFactory.create(currentTimelineItem.event.content()),
|
||||
isMine = currentTimelineItem.event.isOwn(),
|
||||
content = contentFactory.create(currentTimelineItem.event.content),
|
||||
isMine = currentTimelineItem.event.isOwn,
|
||||
groupPosition = groupPosition,
|
||||
reactionsState = currentTimelineItem.computeReactionsState()
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return TimelineItemReactions(aggregatedReactions.toImmutableList())
|
||||
@@ -90,9 +90,9 @@ class TimelineItemEventFactory @Inject constructor(
|
||||
timelineItems.getOrNull(index - 1) as? MatrixTimelineItem.Event
|
||||
val nextTimelineItem =
|
||||
timelineItems.getOrNull(index + 1) as? MatrixTimelineItem.Event
|
||||
val currentSender = currentTimelineItem.event.sender()
|
||||
val previousSender = prevTimelineItem?.event?.sender()
|
||||
val nextSender = nextTimelineItem?.event?.sender()
|
||||
val currentSender = currentTimelineItem.event.sender
|
||||
val previousSender = prevTimelineItem?.event?.sender
|
||||
val nextSender = nextTimelineItem?.event?.sender
|
||||
|
||||
return when {
|
||||
previousSender != currentSender && nextSender == currentSender -> TimelineItemGroupPosition.First
|
||||
|
||||
@@ -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.TimelineItemVirtualModel
|
||||
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
|
||||
|
||||
class TimelineItemDaySeparatorFactory @Inject constructor(private val daySeparatorFormatter: DaySeparatorFormatter) {
|
||||
|
||||
fun create(virtualItem: VirtualTimelineItem.DayDivider): TimelineItemVirtualModel {
|
||||
val formattedDate = daySeparatorFormatter.format(virtualItem.ts.toLong())
|
||||
val formattedDate = daySeparatorFormatter.format(virtualItem.timestamp)
|
||||
return TimelineItemDaySeparatorModel(
|
||||
formattedDate = formattedDate
|
||||
)
|
||||
|
||||
@@ -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.TimelineItemVirtualModel
|
||||
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
|
||||
|
||||
class TimelineItemVirtualFactory @Inject constructor(
|
||||
|
||||
@@ -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.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
@Immutable
|
||||
sealed interface TimelineItem {
|
||||
|
||||
fun identifier(): String = when(this){
|
||||
fun identifier(): String = when (this) {
|
||||
is Event -> id
|
||||
is Virtual -> id
|
||||
}
|
||||
@@ -40,7 +41,7 @@ sealed interface TimelineItem {
|
||||
data class Event(
|
||||
val id: String,
|
||||
val eventId: EventId? = null,
|
||||
val senderId: String,
|
||||
val senderId: UserId,
|
||||
val senderDisplayName: String?,
|
||||
val senderAvatar: AvatarData,
|
||||
val content: TimelineItemEventContent,
|
||||
@@ -52,6 +53,6 @@ sealed interface TimelineItem {
|
||||
|
||||
val showSenderInformation = groupPosition.isNew() && !isMine
|
||||
|
||||
val safeSenderName: String = senderDisplayName ?: senderId
|
||||
val safeSenderName: String = senderDisplayName ?: senderId.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
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(
|
||||
val encryptedMessage: EncryptedMessage
|
||||
val data: UnableToDecryptContent.Data
|
||||
) : TimelineItemEventContent
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
package io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
|
||||
import org.jsoup.Jsoup
|
||||
import org.matrix.rustcomponents.sdk.EncryptedMessage
|
||||
|
||||
class TimelineItemEventContentProvider : PreviewParameterProvider<TimelineItemEventContent> {
|
||||
override val values = sequenceOf(
|
||||
@@ -49,7 +49,7 @@ fun aTimelineItemEmoteContent() = TimelineItemEmoteContent(
|
||||
)
|
||||
|
||||
fun aTimelineItemEncryptedContent() = TimelineItemEncryptedContent(
|
||||
encryptedMessage = EncryptedMessage.Unknown
|
||||
data = UnableToDecryptContent.Data.Unknown
|
||||
)
|
||||
|
||||
fun aTimelineItemNoticeContent() = TimelineItemNoticeContent(
|
||||
|
||||
@@ -30,7 +30,7 @@ open class TimelineItemImageContentProvider : PreviewParameterProvider<TimelineI
|
||||
|
||||
fun aTimelineItemImageContent() = TimelineItemImageContent(
|
||||
body = "a body",
|
||||
imageMeta = MediaResolver.Meta(source = null, kind = MediaResolver.Kind.Content),
|
||||
imageMeta = MediaResolver.Meta(url = null, kind = MediaResolver.Kind.Content),
|
||||
blurhash = null,
|
||||
aspectRatio = 0.5f,
|
||||
)
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
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.nodes.Document
|
||||
import org.matrix.rustcomponents.sdk.FormattedBody
|
||||
import org.matrix.rustcomponents.sdk.MessageFormat
|
||||
|
||||
fun FormattedBody.toHtmlDocument(): Document? {
|
||||
return takeIf { it.format == MessageFormat.HTML }?.body?.let { formattedBody ->
|
||||
|
||||
@@ -170,7 +170,7 @@ private fun aMessageEvent(
|
||||
) = TimelineItem.Event(
|
||||
id = AN_EVENT_ID.value,
|
||||
eventId = AN_EVENT_ID,
|
||||
senderId = A_USER_ID.value,
|
||||
senderId = A_USER_ID,
|
||||
senderDisplayName = A_USER_NAME,
|
||||
senderAvatar = AvatarData(A_USER_ID.value, A_USER_NAME),
|
||||
content = content,
|
||||
|
||||
@@ -33,7 +33,7 @@ internal fun aMessageEvent(
|
||||
) = TimelineItem.Event(
|
||||
id = AN_EVENT_ID.value,
|
||||
eventId = AN_EVENT_ID,
|
||||
senderId = A_USER_ID.value,
|
||||
senderId = A_USER_ID,
|
||||
senderDisplayName = A_USER_NAME,
|
||||
senderAvatar = AvatarData(A_USER_ID.value, A_USER_NAME),
|
||||
content = content,
|
||||
|
||||
@@ -58,7 +58,7 @@ class RoomListPresenter @Inject constructor(
|
||||
}
|
||||
var filter by rememberSaveable { mutableStateOf("") }
|
||||
val roomSummaries by client
|
||||
.roomSummaryDataSource()
|
||||
.roomSummaryDataSource
|
||||
.roomSummaries()
|
||||
.collectAsState()
|
||||
|
||||
@@ -125,7 +125,7 @@ class RoomListPresenter @Inject constructor(
|
||||
// Safe to give bigger size than room list
|
||||
val extendedRangeEnd = range.last + midExtendedRangeSize
|
||||
val extendedRange = IntRange(extendedRangeStart, extendedRangeEnd)
|
||||
client.roomSummaryDataSource().setSlidingSyncRange(extendedRange)
|
||||
client.roomSummaryDataSource.setSlidingSyncRange(extendedRange)
|
||||
}
|
||||
|
||||
private suspend fun mapRoomSummaries(
|
||||
|
||||
@@ -14,23 +14,14 @@
|
||||
* 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
|
||||
import dagger.Provides
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import org.matrix.rustcomponents.sdk.AuthenticationService
|
||||
import java.io.File
|
||||
|
||||
@Module
|
||||
@ContributesTo(AppScope::class)
|
||||
object MatrixModule {
|
||||
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun providesRustAuthenticationService(baseDirectory: File): AuthenticationService {
|
||||
return AuthenticationService(baseDirectory.absolutePath, null, null)
|
||||
/**
|
||||
* 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))
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,6 @@ anvil {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// api(projects.libraries.rustsdk)
|
||||
api(libs.matrix.sdk)
|
||||
implementation(projects.libraries.di)
|
||||
implementation(libs.dagger)
|
||||
implementation(projects.libraries.core)
|
||||
|
||||
@@ -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.room.MatrixRoom
|
||||
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 roomSummaryDataSource: RoomSummaryDataSource
|
||||
fun getRoom(roomId: RoomId): MatrixRoom?
|
||||
fun startSync()
|
||||
fun stopSync()
|
||||
fun roomSummaryDataSource(): RoomSummaryDataSource
|
||||
fun mediaResolver(): MediaResolver
|
||||
suspend fun logout()
|
||||
suspend fun loadUserDisplayName(): Result<String>
|
||||
suspend fun loadUserAvatarURLString(): Result<String>
|
||||
suspend fun loadMediaContentForSource(source: MediaSource): Result<ByteArray>
|
||||
suspend fun loadMediaThumbnailForSource(
|
||||
source: MediaSource,
|
||||
suspend fun loadMediaContent(url: String): Result<ByteArray>
|
||||
suspend fun loadMediaThumbnail(
|
||||
url: String,
|
||||
width: Long,
|
||||
height: Long
|
||||
): Result<ByteArray>
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package io.element.android.libraries.matrix.api.auth
|
||||
|
||||
import org.matrix.rustcomponents.sdk.AuthenticationException
|
||||
|
||||
enum class AuthErrorCode(val value: String) {
|
||||
UNKNOWN("M_UNKNOWN"),
|
||||
USER_DEACTIVATED("M_USER_DEACTIVATED"),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -24,8 +24,8 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
interface MatrixAuthenticationService {
|
||||
fun isLoggedIn(): Flow<Boolean>
|
||||
suspend fun getLatestSessionId(): SessionId?
|
||||
suspend fun restoreSession(sessionId: SessionId): MatrixClient?
|
||||
suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient>
|
||||
fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?>
|
||||
suspend fun setHomeserver(homeserver: String)
|
||||
suspend fun login(username: String, password: String): SessionId
|
||||
suspend fun setHomeserver(homeserver: String): Result<Unit>
|
||||
suspend fun login(username: String, password: String): Result<SessionId>
|
||||
}
|
||||
|
||||
@@ -18,17 +18,10 @@ package io.element.android.libraries.matrix.api.auth
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.rustcomponents.sdk.HomeserverLoginDetails
|
||||
|
||||
@Parcelize
|
||||
data class MatrixHomeServerDetails(
|
||||
val url: String,
|
||||
val supportsPasswordLogin: Boolean,
|
||||
val authenticationIssuer: String?
|
||||
): Parcelable {
|
||||
constructor(homeserverLoginDetails: HomeserverLoginDetails) : this(
|
||||
homeserverLoginDetails.url(),
|
||||
homeserverLoginDetails.supportsPasswordLogin(),
|
||||
homeserverLoginDetails.authenticationIssuer()
|
||||
)
|
||||
}
|
||||
): Parcelable
|
||||
|
||||
@@ -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?
|
||||
)
|
||||
@@ -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?
|
||||
)
|
||||
@@ -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?
|
||||
)
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package io.element.android.libraries.matrix.api.media
|
||||
|
||||
import org.matrix.rustcomponents.sdk.MediaSource
|
||||
|
||||
interface MediaResolver {
|
||||
|
||||
sealed interface Kind {
|
||||
@@ -29,11 +27,10 @@ interface MediaResolver {
|
||||
}
|
||||
|
||||
data class Meta(
|
||||
val source: MediaSource?,
|
||||
val url: String?,
|
||||
val kind: Kind
|
||||
)
|
||||
|
||||
suspend fun resolve(url: String?, kind: Kind): ByteArray?
|
||||
|
||||
suspend fun resolve(meta: Meta): ByteArray?
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
)
|
||||
@@ -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?
|
||||
)
|
||||
@@ -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.timeline.MatrixTimeline
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.io.Closeable
|
||||
|
||||
interface MatrixRoom {
|
||||
interface MatrixRoom: Closeable {
|
||||
val roomId: RoomId
|
||||
val name: String?
|
||||
val bestName: String
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
package io.element.android.libraries.matrix.api.timeline
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import org.matrix.rustcomponents.sdk.EventTimelineItem
|
||||
import org.matrix.rustcomponents.sdk.VirtualTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||
|
||||
sealed interface MatrixTimelineItem {
|
||||
data class Event(val event: EventTimelineItem) : MatrixTimelineItem {
|
||||
val uniqueId: String = event.uniqueIdentifier()
|
||||
val eventId: EventId? = event.eventId()?.let { EventId(it) }
|
||||
val uniqueId: String = event.uniqueIdentifier
|
||||
val eventId: EventId? = event.eventId
|
||||
}
|
||||
|
||||
data class Virtual(val virtual: VirtualTimelineItem) : MatrixTimelineItem
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package io.element.android.libraries.matrix.api.auth
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
import org.matrix.rustcomponents.sdk.AuthenticationException
|
||||
|
||||
class AuthErrorCodeTests {
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ anvil {
|
||||
|
||||
dependencies {
|
||||
// api(projects.libraries.rustsdk)
|
||||
api(libs.matrix.sdk)
|
||||
implementation(libs.matrix.sdk)
|
||||
implementation(projects.libraries.di)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(libs.dagger)
|
||||
|
||||
@@ -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.core.RoomId
|
||||
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.room.RustMatrixRoom
|
||||
import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource
|
||||
import io.element.android.libraries.matrix.impl.sync.SlidingSyncObserverProxy
|
||||
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.withContext
|
||||
import org.matrix.rustcomponents.sdk.Client
|
||||
import org.matrix.rustcomponents.sdk.ClientDelegate
|
||||
import org.matrix.rustcomponents.sdk.MediaSource
|
||||
import org.matrix.rustcomponents.sdk.RequiredState
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncMode
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncRequestListFilters
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncViewBuilder
|
||||
import org.matrix.rustcomponents.sdk.TaskHandle
|
||||
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
@@ -94,7 +95,9 @@ class RustMatrixClient constructor(
|
||||
.sendUpdatesForItems(true)
|
||||
.syncMode(mode = SlidingSyncMode.SELECTIVE)
|
||||
.addRange(0u, 20u)
|
||||
.build()
|
||||
.use {
|
||||
it.build()
|
||||
}
|
||||
|
||||
private val slidingSync = client
|
||||
.slidingSync()
|
||||
@@ -102,10 +105,12 @@ class RustMatrixClient constructor(
|
||||
.withCommonExtensions()
|
||||
.coldCache("ElementX")
|
||||
.addView(visibleRoomsView)
|
||||
.build()
|
||||
.use {
|
||||
it.build()
|
||||
}
|
||||
|
||||
private val slidingSyncObserverProxy = SlidingSyncObserverProxy(coroutineScope)
|
||||
private val roomSummaryDataSource: RustRoomSummaryDataSource =
|
||||
private val rustRoomSummaryDataSource: RustRoomSummaryDataSource =
|
||||
RustRoomSummaryDataSource(
|
||||
slidingSyncObserverProxy.updateSummaryFlow,
|
||||
slidingSync,
|
||||
@@ -113,6 +118,10 @@ class RustMatrixClient constructor(
|
||||
dispatchers,
|
||||
::onRestartSync
|
||||
)
|
||||
|
||||
override val roomSummaryDataSource: RoomSummaryDataSource
|
||||
get() = rustRoomSummaryDataSource
|
||||
|
||||
private var slidingSyncObserverToken: TaskHandle? = null
|
||||
|
||||
private val mediaResolver = RustMediaResolver(this)
|
||||
@@ -120,7 +129,7 @@ class RustMatrixClient constructor(
|
||||
|
||||
init {
|
||||
client.setDelegate(clientDelegate)
|
||||
roomSummaryDataSource.init()
|
||||
rustRoomSummaryDataSource.init()
|
||||
slidingSync.setObserver(slidingSyncObserverProxy)
|
||||
}
|
||||
|
||||
@@ -141,8 +150,6 @@ class RustMatrixClient constructor(
|
||||
)
|
||||
}
|
||||
|
||||
override fun roomSummaryDataSource(): RoomSummaryDataSource = roomSummaryDataSource
|
||||
|
||||
override fun mediaResolver(): MediaResolver = mediaResolver
|
||||
|
||||
override fun startSync() {
|
||||
@@ -154,15 +161,17 @@ class RustMatrixClient constructor(
|
||||
|
||||
override fun stopSync() {
|
||||
if (isSyncing.compareAndSet(true, false)) {
|
||||
slidingSyncObserverToken?.cancel()
|
||||
slidingSyncObserverToken?.use { it.cancel() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
private fun close() {
|
||||
stopSync()
|
||||
slidingSync.setObserver(null)
|
||||
roomSummaryDataSource.close()
|
||||
rustRoomSummaryDataSource.close()
|
||||
client.setDelegate(null)
|
||||
visibleRoomsView.destroy()
|
||||
slidingSync.destroy()
|
||||
}
|
||||
|
||||
override suspend fun logout() = withContext(dispatchers.io) {
|
||||
@@ -174,6 +183,7 @@ class RustMatrixClient constructor(
|
||||
}
|
||||
baseDirectory.deleteSessionDirectory(userID = client.userId())
|
||||
sessionStore.removeSession(client.userId())
|
||||
client.destroy()
|
||||
}
|
||||
|
||||
override suspend fun loadUserDisplayName(): Result<String> = withContext(dispatchers.io) {
|
||||
@@ -189,23 +199,30 @@ class RustMatrixClient constructor(
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
override suspend fun loadMediaContentForSource(source: MediaSource): Result<ByteArray> =
|
||||
override suspend fun loadMediaContent(url: String): Result<ByteArray> =
|
||||
withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
client.getMediaContent(source).toUByteArray().toByteArray()
|
||||
mediaSourceFromUrl(url).use { source ->
|
||||
client.getMediaContent(source).toUByteArray().toByteArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
override suspend fun loadMediaThumbnailForSource(
|
||||
source: MediaSource,
|
||||
override suspend fun loadMediaThumbnail(
|
||||
url: String,
|
||||
width: Long,
|
||||
height: Long
|
||||
): Result<ByteArray> =
|
||||
withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
client.getMediaThumbnail(source, width.toULong(), height.toULong()).toUByteArray()
|
||||
.toByteArray()
|
||||
mediaSourceFromUrl(url).use { source ->
|
||||
client.getMediaThumbnail(
|
||||
source = source,
|
||||
width = width.toULong(),
|
||||
height = height.toULong()
|
||||
).toUByteArray().toByteArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.impl.auth
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
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.SingleIn
|
||||
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.UserId
|
||||
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.SessionStore
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -34,14 +34,13 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.AuthenticationService
|
||||
import org.matrix.rustcomponents.sdk.Client
|
||||
import org.matrix.rustcomponents.sdk.ClientBuilder
|
||||
import org.matrix.rustcomponents.sdk.Session
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import org.matrix.rustcomponents.sdk.AuthenticationService as RustAuthenticationService
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
@SingleIn(AppScope::class)
|
||||
@@ -50,9 +49,9 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
private val sessionStore: SessionStore,
|
||||
private val authService: AuthenticationService,
|
||||
) : MatrixAuthenticationService {
|
||||
|
||||
private val authService: RustAuthenticationService = RustAuthenticationService(baseDirectory.absolutePath, null, null)
|
||||
private var currentHomeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
|
||||
|
||||
override fun isLoggedIn(): Flow<Boolean> {
|
||||
@@ -63,10 +62,10 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
sessionStore.getLatestSession()?.userId?.let { UserId(it) }
|
||||
}
|
||||
|
||||
override suspend fun restoreSession(sessionId: SessionId) = withContext(coroutineDispatchers.io) {
|
||||
val sessionData = sessionStore.getSession(sessionId.value)
|
||||
if (sessionData != null) {
|
||||
try {
|
||||
override suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient> = withContext(coroutineDispatchers.io) {
|
||||
runCatching {
|
||||
val sessionData = sessionStore.getSession(sessionId.value)
|
||||
if (sessionData != null) {
|
||||
val client = ClientBuilder()
|
||||
.basePath(baseDirectory.absolutePath)
|
||||
.homeserverUrl(sessionData.homeserverUrl)
|
||||
@@ -74,36 +73,39 @@ class RustMatrixAuthenticationService @Inject constructor(
|
||||
.use { it.build() }
|
||||
client.restoreSession(sessionData.toSession())
|
||||
createMatrixClient(client)
|
||||
} catch (throwable: Throwable) {
|
||||
logError(throwable)
|
||||
null
|
||||
} else {
|
||||
throw IllegalStateException("No session to restore with id $sessionId")
|
||||
}
|
||||
} else null
|
||||
}.mapFailure { failure ->
|
||||
failure.mapAuthenticationException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?> = currentHomeserver
|
||||
|
||||
override suspend fun setHomeserver(homeserver: String) {
|
||||
override suspend fun setHomeserver(homeserver: String): Result<Unit> =
|
||||
withContext(coroutineDispatchers.io) {
|
||||
authService.configureHomeserver(homeserver)
|
||||
val homeServerDetails = authService.homeserverDetails()?.use { MatrixHomeServerDetails(it) }
|
||||
if (homeServerDetails != null) {
|
||||
currentHomeserver.value = homeServerDetails.copy(url = homeserver)
|
||||
runCatching {
|
||||
authService.configureHomeserver(homeserver)
|
||||
val homeServerDetails = authService.homeserverDetails()?.map()
|
||||
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) {
|
||||
val client = try {
|
||||
authService.login(username, password, "ElementX Android", null)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "Fail login")
|
||||
throw failure
|
||||
runCatching {
|
||||
val client = authService.login(username, password, "ElementX Android", null)
|
||||
val sessionData = client.use { it.session().toSessionData() }
|
||||
sessionStore.storeData(sessionData)
|
||||
SessionId(sessionData.userId)
|
||||
}
|
||||
val sessionData = client.use { it.session().toSessionData() }
|
||||
sessionStore.storeData(sessionData)
|
||||
SessionId(sessionData.userId)
|
||||
}.mapFailure { failure ->
|
||||
failure.mapAuthenticationException()
|
||||
}
|
||||
|
||||
private fun createMatrixClient(client: Client): MatrixClient {
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
@@ -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()
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -14,19 +14,23 @@
|
||||
* 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
|
||||
import org.matrix.rustcomponents.sdk.TimelineItem
|
||||
interface MediaResolver {
|
||||
|
||||
fun TimelineItem.asMatrixTimelineItem(): MatrixTimelineItem {
|
||||
val asEvent = asEvent()
|
||||
if (asEvent != null) {
|
||||
return MatrixTimelineItem.Event(asEvent)
|
||||
sealed interface Kind {
|
||||
data class Thumbnail(val width: Int, val height: Int) : Kind {
|
||||
constructor(size: Int) : this(size, size)
|
||||
}
|
||||
|
||||
object Content : Kind
|
||||
}
|
||||
val asVirtual = asVirtual()
|
||||
if (asVirtual != null) {
|
||||
return MatrixTimelineItem.Virtual(asVirtual)
|
||||
}
|
||||
return MatrixTimelineItem.Other
|
||||
|
||||
data class Meta(
|
||||
val url: String?,
|
||||
val kind: Kind
|
||||
)
|
||||
|
||||
suspend fun resolve(url: String?, kind: Kind): ByteArray?
|
||||
|
||||
}
|
||||
@@ -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() }
|
||||
@@ -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.media.MediaResolver
|
||||
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
|
||||
|
||||
internal class RustMediaResolver(private val client: MatrixClient) : MediaResolver {
|
||||
|
||||
override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? {
|
||||
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) {
|
||||
is MediaResolver.Kind.Content -> client.loadMediaContentForSource(source)
|
||||
is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnailForSource(
|
||||
source,
|
||||
is MediaResolver.Kind.Content -> client.loadMediaContent(url)
|
||||
is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnail(
|
||||
url,
|
||||
kind.width.toLong(),
|
||||
kind.height.toLong()
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -16,16 +16,16 @@
|
||||
|
||||
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.room.RoomSummaryDetails
|
||||
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
|
||||
import org.matrix.rustcomponents.sdk.Room
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncRoom
|
||||
|
||||
class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory()) {
|
||||
|
||||
fun create(slidingSyncRoom: SlidingSyncRoom, room: Room?): RoomSummaryDetails {
|
||||
val latestRoomMessage = slidingSyncRoom.latestRoomMessage()?.let {
|
||||
val latestRoomMessage = slidingSyncRoom.latestRoomMessage()?.use {
|
||||
roomMessageFactory.create(it)
|
||||
}
|
||||
val computedLastMessage = when {
|
||||
@@ -38,7 +38,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto
|
||||
name = slidingSyncRoom.name() ?: slidingSyncRoom.roomId(),
|
||||
isDirect = slidingSyncRoom.isDm() ?: false,
|
||||
avatarURLString = room?.avatarUrl(),
|
||||
unreadNotificationCount = slidingSyncRoom.unreadNotifications().notificationCount().toInt(),
|
||||
unreadNotificationCount = slidingSyncRoom.unreadNotifications().use { it.notificationCount().toInt() },
|
||||
lastMessage = computedLastMessage,
|
||||
lastMessageTimestamp = latestRoomMessage?.originServerTs
|
||||
)
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
package io.element.android.libraries.matrix.impl.room
|
||||
|
||||
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.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
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.flow.Flow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
@@ -62,6 +62,10 @@ class RustMatrixRoom(
|
||||
coroutineDispatchers = coroutineDispatchers
|
||||
)
|
||||
}
|
||||
override fun close() {
|
||||
innerRoom.destroy()
|
||||
slidingSyncRoom.destroy()
|
||||
}
|
||||
|
||||
override val roomId = RoomId(innerRoom.id())
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
package io.element.android.libraries.matrix.impl.room
|
||||
|
||||
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.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.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
@@ -179,10 +179,14 @@ internal class RustRoomSummaryDataSource(
|
||||
}
|
||||
|
||||
private fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary {
|
||||
val room = slidingSync.getRoom(identifier) ?: return RoomSummary.Empty(identifier)
|
||||
return RoomSummary.Filled(
|
||||
details = roomSummaryDetailsFactory.create(room, room.fullRoom())
|
||||
val slidingSyncRoom = slidingSync.getRoom(identifier) ?: return RoomSummary.Empty(identifier)
|
||||
val fullRoom = slidingSyncRoom.fullRoom()
|
||||
val roomSummary = RoomSummary.Filled(
|
||||
details = roomSummaryDetailsFactory.create(slidingSyncRoom, fullRoom)
|
||||
)
|
||||
fullRoom?.destroy()
|
||||
slidingSyncRoom.destroy()
|
||||
return roomSummary
|
||||
}
|
||||
|
||||
private suspend fun updateRoomSummaries(block: MutableList<RoomSummary>.() -> Unit) =
|
||||
|
||||
@@ -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.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -25,14 +26,15 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.TimelineChange
|
||||
import org.matrix.rustcomponents.sdk.TimelineDiff
|
||||
import org.matrix.rustcomponents.sdk.TimelineItem
|
||||
import org.matrix.rustcomponents.sdk.TimelineListener
|
||||
import org.matrix.rustcomponents.sdk.VirtualTimelineItem
|
||||
|
||||
internal class MatrixTimelineDiffProcessor(
|
||||
private val paginationState: MutableStateFlow<MatrixTimeline.PaginationState>,
|
||||
private val timelineItems: MutableStateFlow<List<MatrixTimelineItem>>,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val diffDispatcher: CoroutineDispatcher,
|
||||
private val timelineItemFactory: MatrixTimelineItemMapper,
|
||||
) : TimelineListener {
|
||||
|
||||
override fun onUpdate(update: TimelineDiff) {
|
||||
@@ -117,4 +119,9 @@ internal class MatrixTimelineDiffProcessor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TimelineItem.asMatrixTimelineItem(): MatrixTimelineItem {
|
||||
return timelineItemFactory.map(this)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,15 @@
|
||||
package io.element.android.libraries.matrix.impl.timeline
|
||||
|
||||
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.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
|
||||
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.FlowPreview
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -52,11 +56,21 @@ class RustMatrixTimeline(
|
||||
MatrixTimeline.PaginationState(canBackPaginate = true, isBackPaginating = false)
|
||||
)
|
||||
|
||||
private val timelineItemFactory = MatrixTimelineItemMapper(
|
||||
virtualTimelineItemMapper = VirtualTimelineItemMapper(),
|
||||
eventTimelineItemMapper = EventTimelineItemMapper(
|
||||
contentMapper = TimelineEventContentMapper(
|
||||
eventMessageMapper = EventMessageMapper()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
private val innerTimelineListener = MatrixTimelineDiffProcessor(
|
||||
paginationState = paginationState,
|
||||
timelineItems = timelineItems,
|
||||
coroutineScope = coroutineScope,
|
||||
diffDispatcher = coroutineDispatchers.diffUpdateDispatcher
|
||||
diffDispatcher = coroutineDispatchers.diffUpdateDispatcher,
|
||||
timelineItemFactory = timelineItemFactory,
|
||||
)
|
||||
|
||||
private val listenerTokens = TaskHandleBag()
|
||||
@@ -83,7 +97,7 @@ class RustMatrixTimeline(
|
||||
val result = addListener(innerTimelineListener)
|
||||
result
|
||||
.onSuccess { timelineItems ->
|
||||
val matrixTimelineItems = timelineItems.map { it.asMatrixTimelineItem() }
|
||||
val matrixTimelineItems = timelineItems.map(timelineItemFactory::map)
|
||||
withContext(coroutineDispatchers.diffUpdateDispatcher) {
|
||||
this@RustMatrixTimeline.timelineItems.value = matrixTimelineItems
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,10 @@ class TaskHandleBag(private val tokens: MutableSet<TaskHandle> = CopyOnWriteArra
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
tokens.forEach { it.cancel() }
|
||||
tokens.forEach {
|
||||
it.cancel()
|
||||
it.destroy()
|
||||
}
|
||||
tokens.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.FakeRoomSummaryDataSource
|
||||
import kotlinx.coroutines.delay
|
||||
import org.matrix.rustcomponents.sdk.MediaSource
|
||||
|
||||
class FakeMatrixClient(
|
||||
override val sessionId: SessionId = A_SESSION_ID,
|
||||
private val userDisplayName: Result<String> = Result.success(A_USER_NAME),
|
||||
private val userAvatarURLString: Result<String> = Result.success(AN_AVATAR_URL),
|
||||
val roomSummaryDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource()
|
||||
override val roomSummaryDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource()
|
||||
) : MatrixClient {
|
||||
|
||||
private var logoutFailure: Throwable? = null
|
||||
@@ -45,10 +44,6 @@ class FakeMatrixClient(
|
||||
|
||||
override fun stopSync() = Unit
|
||||
|
||||
override fun roomSummaryDataSource(): RoomSummaryDataSource {
|
||||
return roomSummaryDataSource
|
||||
}
|
||||
|
||||
override fun mediaResolver(): MediaResolver {
|
||||
return FakeMediaResolver()
|
||||
}
|
||||
@@ -70,13 +65,11 @@ class FakeMatrixClient(
|
||||
return userAvatarURLString
|
||||
}
|
||||
|
||||
override suspend fun loadMediaContentForSource(source: MediaSource): Result<ByteArray> {
|
||||
override suspend fun loadMediaContent(url: String): Result<ByteArray> {
|
||||
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))
|
||||
}
|
||||
|
||||
override fun close() = Unit
|
||||
}
|
||||
|
||||
@@ -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.MatrixHomeServerDetails
|
||||
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 kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -41,8 +40,8 @@ class FakeAuthenticationService : MatrixAuthenticationService {
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun restoreSession(sessionId: SessionId): MatrixClient? {
|
||||
return null
|
||||
override suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient> {
|
||||
return Result.failure(IllegalStateException())
|
||||
}
|
||||
|
||||
override fun getHomeserverDetails(): StateFlow<MatrixHomeServerDetails?> {
|
||||
@@ -53,15 +52,21 @@ class FakeAuthenticationService : MatrixAuthenticationService {
|
||||
this.homeserver.value = homeserver
|
||||
}
|
||||
|
||||
override suspend fun setHomeserver(homeserver: String) {
|
||||
override suspend fun setHomeserver(homeserver: String): Result<Unit> {
|
||||
changeServerError?.let { throw it }
|
||||
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)
|
||||
loginError?.let { throw it }
|
||||
return A_USER_ID
|
||||
return loginError.let { loginError ->
|
||||
if (loginError == null) {
|
||||
Result.success(A_USER_ID)
|
||||
} else {
|
||||
Result.failure(loginError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun givenLoginError(throwable: Throwable?) {
|
||||
|
||||
@@ -22,8 +22,4 @@ class FakeMediaResolver : MediaResolver {
|
||||
override suspend fun resolve(url: String?, kind: MediaResolver.Kind): ByteArray? {
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun resolve(meta: MediaResolver.Meta): ByteArray? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,4 +87,6 @@ class FakeMatrixRoom(
|
||||
delay(100)
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
override fun close() = Unit
|
||||
}
|
||||
|
||||
@@ -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.matrix.api.media.MediaResolver
|
||||
import org.matrix.rustcomponents.sdk.mediaSourceFromUrl
|
||||
|
||||
fun AvatarData.toMetadata(): MediaResolver.Meta {
|
||||
val mediaSource = url?.let { mediaSourceFromUrl(it) }
|
||||
return MediaResolver.Meta(source = mediaSource, kind = MediaResolver.Kind.Thumbnail(size.value))
|
||||
return MediaResolver.Meta(url = url, kind = MediaResolver.Kind.Thumbnail(size.value))
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ internal class MediaFetcher(
|
||||
) : Fetcher {
|
||||
|
||||
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)
|
||||
return imageLoader.components.newFetcher(byteBuffer, options, imageLoader)?.first?.fetch()
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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.sessionstorage.impl.memory.InMemorySessionStore
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.matrix.rustcomponents.sdk.AuthenticationService
|
||||
import java.io.File
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
@@ -46,7 +45,6 @@ class MainActivity : ComponentActivity() {
|
||||
coroutineScope = Singleton.appScope,
|
||||
coroutineDispatchers = Singleton.coroutineDispatchers,
|
||||
sessionStore = InMemorySessionStore(),
|
||||
authService = AuthenticationService(baseDirectory.absolutePath, null, null),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -72,7 +70,7 @@ class MainActivity : ComponentActivity() {
|
||||
} else {
|
||||
val matrixClient = runBlocking {
|
||||
val sessionId = matrixAuthenticationService.getLatestSessionId()!!
|
||||
matrixAuthenticationService.restoreSession(sessionId)
|
||||
matrixAuthenticationService.restoreSession(sessionId).getOrNull()
|
||||
}
|
||||
RoomListScreen(matrixClient = matrixClient!!).Content(modifier)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user