[MatrixSDK] start mapping all the timeline objects

This commit is contained in:
ganfra 2023-03-10 18:32:46 +01:00
parent 98911a12ad
commit fb85f35525
37 changed files with 1054 additions and 49 deletions

View file

@ -47,7 +47,7 @@ class TimelineItemContentMessageFactory @Inject constructor() {
TimelineItemImageContent(
body = messageType.content.body,
imageMeta = MediaResolver.Meta(
source = messageType.content.source,
url = messageType.content.source,
kind = MediaResolver.Kind.Content
),
blurhash = messageType.content.info?.blurhash,

View file

@ -19,13 +19,13 @@ package io.element.android.features.messages.impl.timeline.factories.virtual
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModel
import io.element.android.features.messages.impl.timeline.model.virtual.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
)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,8 +16,6 @@
package io.element.android.libraries.matrix.api.media
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?
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,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 TimelineEventContent
data class TimelineEventMessageContent(
val body: String,
val inReplyTo: UserId?,
val isEdited: Boolean,
val content: MessageContent?
) : TimelineEventContent
object RedactedContent : TimelineEventContent
data class StickerContent(
val body: String,
val info: ImageInfo,
val url: String
) : TimelineEventContent
sealed interface EncryptedMessage {
data class OlmV1Curve25519AesSha2(
val senderKey: String
) : EncryptedMessage
data class MegolmV1AesSha2(
val sessionId: String
) : EncryptedMessage
object Unknown : EncryptedMessage
}
data class UnableToDecryptContent(
val message: EncryptedMessage
) : TimelineEventContent
data class RoomMembership(
val userId: UserId,
val change: MembershipChange?
) : TimelineEventContent
data class ProfileChange(
val displayName: String?,
val prevDisplayName: String?,
val avatarUrl: String?,
val prevAvatarUrl: String?
) : TimelineEventContent
data class State(
val stateKey: String,
val content: OtherState
) : TimelineEventContent
data class FailedToParseMessageLike(
val eventType: String,
val error: String
) : TimelineEventContent
data class FailedToParseState(
val eventType: String,
val stateKey: String,
val error: String
) : TimelineEventContent
object UnknownContent : TimelineEventContent
sealed interface MessageContent
object UnknownMessageContent : MessageContent
enum class MessageFormat {
HTML, UNKNOWN
}
data class FormattedBody(
val format: MessageFormat,
val body: String
)
data class EmoteMessageContent(
val body: String,
val formatted: FormattedBody?
) : MessageContent
data class ImageMessageContent(
val body: String,
val url: String,
val info: ImageInfo?
) : MessageContent
data class AudioMessageContent(
var body: String,
var url: String,
var info: AudioInfo?
) : MessageContent
data class VideoMessageContent(
val body: String,
val url: String,
val info: VideoInfo?
) : MessageContent
data class FileMessageContent(
val body: String,
val url: String,
val info: FileInfo?
) : MessageContent
data class NoticeMessageContent(
val body: String,
val formatted: FormattedBody?
) : MessageContent
data class TextMessageContent(
val body: String,
val formatted: FormattedBody?
) : MessageContent
enum class MembershipChange {
NONE,
ERROR,
JOINED,
LEFT,
BANNED,
UNBANNED,
KICKED,
INVITED,
KICKED_AND_BANNED,
INVITATION_ACCEPTED,
INVITATION_REJECTED,
INVITATION_REVOKED,
KNOCKED,
KNOCK_ACCEPTED,
KNOCK_RETRACTED,
KNOCK_DENIED,
NOT_IMPLEMENTED;
}
sealed interface OtherState {
object PolicyRuleRoom : OtherState
object PolicyRuleServer : OtherState
object PolicyRuleUser : OtherState
object RoomAliases : OtherState
data class RoomAvatar(
val url: String?
) : OtherState
object RoomCanonicalAlias : OtherState
object RoomCreate : OtherState
object RoomEncryption : OtherState
object RoomGuestAccess : OtherState
object RoomHistoryVisibility : OtherState
object RoomJoinRules : OtherState
data class RoomName(
val name: String?
) : OtherState
object RoomPinnedEvents : OtherState
object RoomPowerLevels : OtherState
object RoomServerAcl : OtherState
data class RoomThirdPartyInvite(
val displayName: String?
) : OtherState
object RoomTombstone : OtherState
data class RoomTopic(
val `topic`: String?
) : OtherState
object SpaceChild : OtherState
object SpaceParent : OtherState
data class Custom(
val eventType: String
) : OtherState
}

View file

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

View file

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

View file

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

View file

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

View file

@ -14,19 +14,23 @@
* limitations under the License.
*/
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?
}

View file

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

View file

@ -18,23 +18,23 @@ 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.MediaSource
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))
return mediaSourceFromUrl(url).use { mediaSource ->
resolve(mediaSource, kind)
}
}
override suspend fun resolve(meta: MediaResolver.Meta): ByteArray? {
val source = meta.source ?: return null
val kind = meta.kind
private suspend fun resolve(mediaSource: MediaSource, kind: MediaResolver.Kind): ByteArray? {
return when (kind) {
is MediaResolver.Kind.Content -> client.loadMediaContentForSource(source)
is MediaResolver.Kind.Content -> client.loadMediaContentForSource(mediaSource)
is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnailForSource(
source,
mediaSource,
kind.width.toLong(),
kind.height.toLong()
)

View file

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

View file

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

View file

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

View file

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

View file

@ -17,11 +17,15 @@
package io.element.android.libraries.matrix.impl.timeline
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
}

View file

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

View file

@ -0,0 +1,80 @@
/*
* 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 io.element.android.libraries.matrix.api.timeline.item.event.TimelineEventContent
import org.matrix.rustcomponents.sdk.Reaction
import org.matrix.rustcomponents.sdk.EventSendState as RustEventSendState
import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem
import org.matrix.rustcomponents.sdk.ProfileTimelineDetails as RustProfileTimelineDetails
class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper()) {
fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.use {
EventTimelineItem(
uniqueIdentifier = eventTimelineItem.uniqueIdentifier(),
eventId = eventTimelineItem.eventId()?.let { EventId(it) },
isEditable = eventTimelineItem.isEditable(),
isLocal = eventTimelineItem.isLocal(),
isOwn = eventTimelineItem.isOwn(),
isRemote = eventTimelineItem.isRemote(),
localSendState = eventTimelineItem.localSendState()?.map(),
reactions = eventTimelineItem.reactions().map(),
sender = UserId(eventTimelineItem.sender()),
senderProfile = eventTimelineItem.senderProfile().map(),
timestamp = eventTimelineItem.timestamp().toLong(),
content = contentMapper.map(eventTimelineItem.content())
)
}
}
fun RustProfileTimelineDetails.map(): ProfileTimelineDetails {
return when (this) {
RustProfileTimelineDetails.Pending -> ProfileTimelineDetails.Pending
RustProfileTimelineDetails.Unavailable -> ProfileTimelineDetails.Unavailable
is RustProfileTimelineDetails.Error -> ProfileTimelineDetails.Error(message)
is RustProfileTimelineDetails.Ready -> ProfileTimelineDetails.Ready(
displayName = displayName,
displayNameAmbiguous = displayNameAmbiguous,
avatarUrl = avatarUrl
)
}
}
fun RustEventSendState?.map(): EventSendState? {
return when (this) {
null -> null
RustEventSendState.NotSendYet -> EventSendState.NotSendYet
is RustEventSendState.SendingFailed -> EventSendState.SendingFailed(error)
is RustEventSendState.Sent -> EventSendState.Sent(EventId(eventId))
}
}
private fun List<Reaction>?.map(): List<EventReaction> {
return this?.map {
EventReaction(
key = it.key,
count = it.count.toLong()
)
} ?: emptyList()
}

View file

@ -0,0 +1,90 @@
/*
* 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.FailedToParseMessageLike
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseState
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChange
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembership
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineEventContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
import io.element.android.libraries.matrix.impl.media.map
import org.matrix.rustcomponents.sdk.TimelineItemContent
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMapper = EventMessageMapper()) {
fun map(content: TimelineItemContent): TimelineEventContent = content.use {
when (val kind = content.kind()) {
is TimelineItemContentKind.FailedToParseMessageLike -> {
FailedToParseMessageLike(
eventType = kind.eventType,
error = kind.error
)
}
is TimelineItemContentKind.FailedToParseState -> {
FailedToParseState(
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 -> {
ProfileChange(
displayName = kind.displayName,
prevDisplayName = kind.prevDisplayName,
avatarUrl = kind.avatarUrl,
prevAvatarUrl = kind.prevAvatarUrl
)
}
TimelineItemContentKind.RedactedMessage -> {
RedactedContent
}
is TimelineItemContentKind.RoomMembership -> {
RoomMembership(
UserId(kind.userId),
MembershipChange.JOINED
)
}
is TimelineItemContentKind.State -> {
UnknownContent
}
is TimelineItemContentKind.Sticker -> {
StickerContent(
body = kind.body,
info = kind.info.map(),
url = kind.url
)
}
is TimelineItemContentKind.UnableToDecrypt -> {
UnknownContent
}
}
}
}

View file

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

View file

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

View file

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

View file

@ -18,9 +18,7 @@ package io.element.android.libraries.matrix.ui.media
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.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))
}

View file

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

View file

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