From bdf754c26bad9094fe8ca81495319ffe2775f13d Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 9 Mar 2023 19:58:17 +0100 Subject: [PATCH 01/10] [MatrixSDK] makes sure to release FFI objects from RustMatrixClient --- .../roomlist/impl/RoomListPresenter.kt | 4 +-- .../libraries/matrix/api/MatrixClient.kt | 4 +-- .../libraries/matrix/impl/RustMatrixClient.kt | 34 ++++++++++++------- .../libraries/matrix/test/FakeMatrixClient.kt | 8 +---- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 1ec5dc6bd2..73520e08fb 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -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( diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 71d9e2a60c..52ea513ff2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -24,12 +24,12 @@ 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 diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index e75dd756a2..2539df41e4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -20,14 +20,14 @@ 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 @@ -38,6 +38,7 @@ 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.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) { @@ -172,6 +181,7 @@ class RustMatrixClient constructor( } catch (failure: Throwable) { Timber.e(failure, "Fail to call logout on HS. Still delete local files.") } + client.destroy() baseDirectory.deleteSessionDirectory(userID = client.userId()) sessionStore.removeSession(client.userId()) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index d5326fc725..7bdaec6c0b 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -32,7 +32,7 @@ class FakeMatrixClient( override val sessionId: SessionId = A_SESSION_ID, private val userDisplayName: Result = Result.success(A_USER_NAME), private val userAvatarURLString: Result = Result.success(AN_AVATAR_URL), - val roomSummaryDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource() + override val roomSummaryDataSource: RoomSummaryDataSource = FakeRoomSummaryDataSource() ) : MatrixClient { private var logoutFailure: Throwable? = null @@ -45,10 +45,6 @@ class FakeMatrixClient( override fun stopSync() = Unit - override fun roomSummaryDataSource(): RoomSummaryDataSource { - return roomSummaryDataSource - } - override fun mediaResolver(): MediaResolver { return FakeMediaResolver() } @@ -77,6 +73,4 @@ class FakeMatrixClient( override suspend fun loadMediaThumbnailForSource(source: MediaSource, width: Long, height: Long): Result { return Result.success(ByteArray(0)) } - - override fun close() = Unit } From c7519149fd9d42ba083fdd017072d4fce926a7d7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 9 Mar 2023 20:08:53 +0100 Subject: [PATCH 02/10] [MatrixSDK] makes sure to release FFI objects from RustRoomSummaryDataSource --- .../matrix/impl/room/RoomSummaryDetailsFactory.kt | 6 +++--- .../matrix/impl/room/RustRoomSummaryDataSource.kt | 14 +++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryDetailsFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryDetailsFactory.kt index 26a14a3b86..e40729d676 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryDetailsFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSummaryDetailsFactory.kt @@ -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 ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt index cae0515f5a..8691c06664 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt @@ -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.() -> Unit) = From b1c116b8833ef5379125588a95d12fe6e8bf53d6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 9 Mar 2023 20:22:16 +0100 Subject: [PATCH 03/10] [MatrixSDK] makes sure to release FFI objects from RustMatrixRoom by calling close --- .../main/kotlin/io/element/android/appnav/RoomFlowNode.kt | 1 + .../element/android/libraries/matrix/api/room/MatrixRoom.kt | 3 ++- .../android/libraries/matrix/impl/room/RustMatrixRoom.kt | 6 +++++- .../android/libraries/matrix/test/room/FakeMatrixRoom.kt | 2 ++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt index aa5a972de9..ab18c30e09 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt @@ -71,6 +71,7 @@ class RoomFlowNode @AssistedInject constructor( }, onDestroy = { Timber.v("OnDestroy") + inputs.room.close() plugins().forEach { it.onFlowReleased(inputs.room) } } ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index f29770a171..4215fb08c9 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -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 diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 46d7ff4b1b..a86c08dec0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -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()) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 95eb07789e..3d11e4d75b 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -87,4 +87,6 @@ class FakeMatrixRoom( delay(100) return Result.success(Unit) } + + override fun close() = Unit } From 8ccd21d856cbad07c5b8dc1a6ab871c44cda56a4 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 10 Mar 2023 18:32:46 +0100 Subject: [PATCH 04/10] [MatrixSDK] start mapping all the timeline objects --- .../TimelineItemContentMessageFactory.kt | 2 +- .../TimelineItemDaySeparatorFactory.kt | 4 +- .../virtual/TimelineItemVirtualFactory.kt | 9 +- .../event/TimelineItemImageContentProvider.kt | 2 +- .../libraries/matrix/api/media/AudioInfo.kt | 22 ++ .../libraries/matrix/api/media/FileInfo.kt | 24 ++ .../libraries/matrix/api/media/ImageInfo.kt | 27 +++ .../matrix/api/media/MediaResolver.kt | 5 +- .../matrix/api/media/ThumbnailInfo.kt | 24 ++ .../libraries/matrix/api/media/VideoInfo.kt | 28 +++ .../matrix/api/timeline/MatrixTimelineItem.kt | 8 +- .../api/timeline/item/event/EventReaction.kt | 22 ++ .../api/timeline/item/event/EventSendState.kt | 31 +++ .../timeline/item/event/EventTimelineItem.kt | 35 +++ .../item/event/ProfileTimelineDetails.kt | 33 +++ .../item/event/TimelineEventContent.kt | 212 ++++++++++++++++++ .../item/virtual/VirtualTimelineItem.kt | 30 +++ .../libraries/matrix/impl/media/AudioInfo.kt | 25 +++ .../libraries/matrix/impl/media/FileInfo.kt | 29 +++ .../libraries/matrix/impl/media/ImageInfo.kt | 30 +++ .../MediaResolver.kt} | 28 ++- .../matrix/impl/media/MediaSource.kt | 22 ++ .../matrix/impl/media/RustMediaResolver.kt | 14 +- .../matrix/impl/media/ThumbnailInfo.kt | 27 +++ .../libraries/matrix/impl/media/VideoInfo.kt | 31 +++ .../timeline/MatrixTimelineDiffProcessor.kt | 9 +- .../impl/timeline/MatrixTimelineItemMapper.kt | 42 ++++ .../impl/timeline/RustMatrixTimeline.kt | 20 +- .../timeline/item/event/EventMessageMapper.kt | 89 ++++++++ .../item/event/EventTimelineItemMapper.kt | 80 +++++++ .../item/event/TimelineEventContentMapper.kt | 90 ++++++++ .../item/virtual/VirtualTimelineItemMapper.kt | 32 +++ .../matrix/impl/util/TaskHandleBag.kt | 5 +- .../matrix/test/media/FakeMediaResolver.kt | 4 - .../matrix/ui/media/AvatarDataExt.kt | 4 +- .../libraries/matrix/ui/media/MediaFetcher.kt | 2 +- .../libraries/matrix/ui/media/MediaKeyer.kt | 2 +- 37 files changed, 1054 insertions(+), 49 deletions(-) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioInfo.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/FileInfo.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/ImageInfo.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/ThumbnailInfo.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/VideoInfo.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventReaction.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventSendState.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineEventContent.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioInfo.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt rename libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/{timeline/MatrixTimelineItem.kt => media/MediaResolver.kt} (54%) create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaSource.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ThumbnailInfo.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 0f9f216699..3146d24a10 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -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, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt index 5192f190db..5a778fe28a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt @@ -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 ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt index b71a911086..a6ee1de9b2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt @@ -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 } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt index 3aa9e4902f..ba79c9988f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt @@ -30,7 +30,7 @@ open class TimelineItemImageContentProvider : PreviewParameterProvider, + val sender: UserId, + val senderProfile: ProfileTimelineDetails, + val timestamp: Long, + val content: TimelineEventContent +) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt new file mode 100644 index 0000000000..fa22d3cf54 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt @@ -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 +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineEventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineEventContent.kt new file mode 100644 index 0000000000..4fc2f437f7 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineEventContent.kt @@ -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 +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt new file mode 100644 index 0000000000..9a700ac98b --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt @@ -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 +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioInfo.kt new file mode 100644 index 0000000000..1108dd1cc8 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioInfo.kt @@ -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() +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt new file mode 100644 index 0000000000..98c96c4d9a --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt @@ -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() +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt new file mode 100644 index 0000000000..27ab6d656a --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt @@ -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 +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItem.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaResolver.kt similarity index 54% rename from libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItem.kt rename to libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaResolver.kt index 70cbc165f7..ca840ee44f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItem.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaResolver.kt @@ -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? + } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaSource.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaSource.kt new file mode 100644 index 0000000000..2fc50611e8 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaSource.kt @@ -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() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaResolver.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaResolver.kt index 413ee26f67..8b16b8e551 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaResolver.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaResolver.kt @@ -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() ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ThumbnailInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ThumbnailInfo.kt new file mode 100644 index 0000000000..3f10720132 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ThumbnailInfo.kt @@ -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() +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt new file mode 100644 index 0000000000..0db364f544 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt @@ -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 +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt index f3033a40e1..7d299519a5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt @@ -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, private val timelineItems: MutableStateFlow>, 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) + } + } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt new file mode 100644 index 0000000000..7151f3550b --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt @@ -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 + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index 98ea6e7d3f..4e4c19458d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -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 } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt new file mode 100644 index 0000000000..6cf2e7878f --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt @@ -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 + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt new file mode 100644 index 0000000000..c399dd0b1d --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -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?.map(): List { + return this?.map { + EventReaction( + key = it.key, + count = it.count.toLong() + ) + } ?: emptyList() +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt new file mode 100644 index 0000000000..e40e7f82d5 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -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 + } + } + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt new file mode 100644 index 0000000000..8e2dd9304f --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt @@ -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 + } + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/TaskHandleBag.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/TaskHandleBag.kt index ad2c6ccddd..9a21645351 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/TaskHandleBag.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/TaskHandleBag.kt @@ -27,7 +27,10 @@ class TaskHandleBag(private val tokens: MutableSet = CopyOnWriteArra } fun dispose() { - tokens.forEach { it.cancel() } + tokens.forEach { + it.cancel() + it.destroy() + } tokens.clear() } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaResolver.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaResolver.kt index c642c630f5..4d5ebc8029 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaResolver.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaResolver.kt @@ -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 - } } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataExt.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataExt.kt index f12842eff0..1392a1ff5c 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataExt.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataExt.kt @@ -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)) } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaFetcher.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaFetcher.kt index fa0f42bf9b..6567101162 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaFetcher.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaFetcher.kt @@ -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() } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaKeyer.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaKeyer.kt index 7861649afe..2d4ab683b1 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaKeyer.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaKeyer.kt @@ -33,4 +33,4 @@ internal class MediaKeyer : Keyer { } } -private fun MediaResolver.Meta.toKey() = "${source?.url()}_${kind}" +private fun MediaResolver.Meta.toKey() = "${url}_${kind}" From 38dc36e7bf99a4595efe33e953a48ac10e51ebb2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 13 Mar 2023 20:18:16 +0100 Subject: [PATCH 05/10] [MatrixSDK] finish mapping timeline and makes it compile --- app/build.gradle.kts | 1 + .../io/element/android/appnav/RootFlowNode.kt | 17 ++--- .../android/appnav/di/MatrixClientsHolder.kt | 11 +-- .../impl/changeserver/ChangeServerView.kt | 2 +- .../login/impl/error/ErrorFormatter.kt | 2 +- .../login/impl/root/LoginRootPresenter.kt | 18 +++-- .../impl/timeline/TimelineStateProvider.kt | 3 +- .../event/TimelineItemEncryptedView.kt | 4 +- .../event/TimelineItemContentFactory.kt | 38 ++++++---- ...eItemContentFailedToParseMessageFactory.kt | 4 +- ...ineItemContentFailedToParseStateFactory.kt | 4 +- .../TimelineItemContentMessageFactory.kt | 41 ++++++----- ...TimelineItemContentProfileChangeFactory.kt | 4 +- .../TimelineItemContentRedactedFactory.kt | 4 +- ...imelineItemContentRoomMembershipFactory.kt | 4 +- .../event/TimelineItemContentStateFactory.kt | 4 +- .../TimelineItemContentStickerFactory.kt | 4 +- .../event/TimelineItemContentUTDFactory.kt | 8 +-- .../event/TimelineItemEventFactory.kt | 22 +++--- .../virtual/TimelineItemVirtualFactory.kt | 9 ++- .../impl/timeline/model/TimelineItem.kt | 7 +- .../event/TimelineItemEncryptedContent.kt | 4 +- .../event/TimelineItemEventContentProvider.kt | 4 +- .../impl/timeline/util/toHtmlDocument.kt | 4 +- .../libraries/core/extensions/Result.kt | 27 +++++++ libraries/matrix/api/build.gradle.kts | 2 - .../libraries/matrix/api/MatrixClient.kt | 8 +-- .../matrix/api/auth/AuthErrorCode.kt | 2 - .../api/auth/AuthenticationException.kt | 25 +++++++ .../api/auth/MatrixAuthenticationService.kt | 6 +- .../api/auth/MatrixHomeServerDetails.kt | 9 +-- .../item/event/TimelineEventContent.kt | 70 +++++++++---------- libraries/matrix/impl/build.gradle.kts | 2 +- .../libraries/matrix/impl/RustMatrixClient.kt | 23 +++--- .../impl/auth/AuthenticationException.kt | 31 ++++++++ .../matrix/impl/auth/HomeserverDetails.kt | 28 ++++++++ .../auth/RustMatrixAuthenticationService.kt | 52 +++++++------- .../matrix/impl/media/RustMediaResolver.kt | 14 +--- .../timeline/item/event/EventMessageMapper.kt | 40 +++++------ .../item/event/TimelineEventContentMapper.kt | 30 +++++--- .../libraries/matrix/test/FakeMatrixClient.kt | 5 +- .../test/auth/FakeAuthenticationService.kt | 12 ++-- samples/minimal/build.gradle.kts | 1 + .../android/samples/minimal/MainActivity.kt | 2 +- 44 files changed, 370 insertions(+), 242 deletions(-) create mode 100644 libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e1bddd561b..e71b20a051 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -219,6 +219,7 @@ dependencies { implementation(libs.androidx.activity.compose) implementation(libs.androidx.startup) implementation(libs.coil) + implementation(libs.matrix.sdk) implementation(libs.dagger) kapt(libs.dagger.compiler) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 87263b86f4..8251584308 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -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() { diff --git a/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixClientsHolder.kt b/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixClientsHolder.kt index 2e929b07e5..092cffd630 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixClientsHolder.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixClientsHolder.kt @@ -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") + } } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt index ef8f4d76b3..2e238c7b50 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt @@ -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) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt index 1e83fcbc66..6c652a29c1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt @@ -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( diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootPresenter.kt index a76f197b4a..46a42ad24c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootPresenter.kt @@ -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) = 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.trim()) + .onSuccess { sessionId -> + loggedInState.value = LoggedInState.LoggedIn(sessionId) + } + .onFailure { failure -> + loggedInState.value = LoggedInState.ErrorLoggingIn(failure) + } } private fun updateFormState(formState: MutableState, updateLambda: LoginFormState.() -> LoginFormState) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index bb487845f7..df222911b0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -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( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt index 2d98005eba..4d65c1ebcd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt @@ -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 ) ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt index c3417bc3c9..262ef31d2e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt @@ -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.TimelineEventContent +import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent +import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent import javax.inject.Inject -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: TimelineEventContent): 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 } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseMessageFactory.kt index f5923e34af..4dacb76523 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseMessageFactory.kt @@ -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 } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseStateFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseStateFactory.kt index 7ef7bfecab..7e2bf90050 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseStateFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseStateFactory.kt @@ -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 } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 3146d24a10..c0b4a4f5cc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -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( - url = 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 } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt index 8e7683d51d..689bbcbaa1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt @@ -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 } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRedactedFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRedactedFactory.kt index 968c51070c..dfbbd233c7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRedactedFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRedactedFactory.kt @@ -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 } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt index 037338cbbe..808e0d3b70 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt @@ -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 } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt index e2dcec5a8c..1680006bc1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt @@ -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 } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt index de7ad74ef5..b823791912 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt @@ -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 } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentUTDFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentUTDFactory.kt index 495059be73..3281f6dd9b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentUTDFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentUTDFactory.kt @@ -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) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt index 39c8719db4..c21622b50f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt @@ -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, ): 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 diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt index a6ee1de9b2..100917a05f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt @@ -23,7 +23,6 @@ import io.element.android.features.messages.impl.timeline.model.virtual.Timeline import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel import io.element.android.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 class TimelineItemVirtualFactory @Inject constructor( @@ -43,10 +42,10 @@ class TimelineItemVirtualFactory @Inject constructor( private fun MatrixTimelineItem.Virtual.computeModel(index: Int): TimelineItemVirtualModel { return when (val inner = virtual) { - 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 + is VirtualTimelineItem.DayDivider -> daySeparatorFactory.create(inner) + is VirtualTimelineItem.ReadMarker -> TimelineItemReadMarkerModel + is VirtualTimelineItem.LoadingIndicator -> TimelineItemLoadingModel + is VirtualTimelineItem.TimelineStart -> TimelineItemReadMarkerModel else -> TimelineItemUnknownVirtualModel } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index fbadbf79af..ced4ce1d10 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -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 } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContent.kt index cdb9b1a274..b42c2ac535 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContent.kt @@ -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 diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt index e8df9d9639..7e4be1bbd1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt @@ -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 { override val values = sequenceOf( @@ -49,7 +49,7 @@ fun aTimelineItemEmoteContent() = TimelineItemEmoteContent( ) fun aTimelineItemEncryptedContent() = TimelineItemEncryptedContent( - encryptedMessage = EncryptedMessage.Unknown + data = UnableToDecryptContent.Data.Unknown ) fun aTimelineItemNoticeContent() = TimelineItemNoticeContent( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/toHtmlDocument.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/toHtmlDocument.kt index 5a9a51796a..8031e77a4d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/toHtmlDocument.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/toHtmlDocument.kt @@ -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 -> diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt new file mode 100644 index 0000000000..b63efa781d --- /dev/null +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.core.extensions + +/** + * Can be used to transform some Throwable into some other + */ +inline fun Result.mapFailure(transform: (exception: Throwable) -> Throwable): Result { + return when (val exception = exceptionOrNull()) { + null -> this + else -> Result.failure(transform(exception)) + } +} diff --git a/libraries/matrix/api/build.gradle.kts b/libraries/matrix/api/build.gradle.kts index 26b133e9ff..3a2fd2472a 100644 --- a/libraries/matrix/api/build.gradle.kts +++ b/libraries/matrix/api/build.gradle.kts @@ -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) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 52ea513ff2..a0f6a472e8 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -21,8 +21,6 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.media.MediaResolver import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource -import org.matrix.rustcomponents.sdk.MediaSource -import java.io.Closeable interface MatrixClient { val sessionId: SessionId @@ -34,9 +32,9 @@ interface MatrixClient { suspend fun logout() suspend fun loadUserDisplayName(): Result suspend fun loadUserAvatarURLString(): Result - suspend fun loadMediaContentForSource(source: MediaSource): Result - suspend fun loadMediaThumbnailForSource( - source: MediaSource, + suspend fun loadMediaContent(url: String): Result + suspend fun loadMediaThumbnail( + url: String, width: Long, height: Long ): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCode.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCode.kt index 18dc3a6605..dfc88e6110 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCode.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCode.kt @@ -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"), diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt new file mode 100644 index 0000000000..4c943db0d6 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt @@ -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) +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt index 80ee68e6de..3414c4fb6e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt @@ -24,8 +24,8 @@ import kotlinx.coroutines.flow.StateFlow interface MatrixAuthenticationService { fun isLoggedIn(): Flow suspend fun getLatestSessionId(): SessionId? - suspend fun restoreSession(sessionId: SessionId): MatrixClient? + suspend fun restoreSession(sessionId: SessionId): Result fun getHomeserverDetails(): StateFlow - suspend fun setHomeserver(homeserver: String) - suspend fun login(username: String, password: String): SessionId + suspend fun setHomeserver(homeserver: String): Result + suspend fun login(username: String, password: String): Result } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetails.kt index 430029642e..7f2b074c67 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetails.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetails.kt @@ -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 diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineEventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineEventContent.kt index 4fc2f437f7..12bd98568a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineEventContent.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineEventContent.kt @@ -24,11 +24,11 @@ import io.element.android.libraries.matrix.api.media.VideoInfo sealed interface TimelineEventContent -data class TimelineEventMessageContent( +data class MessageContent( val body: String, val inReplyTo: UserId?, val isEdited: Boolean, - val content: MessageContent? + val type: MessageType? ) : TimelineEventContent object RedactedContent : TimelineEventContent @@ -39,45 +39,45 @@ data class StickerContent( val url: String ) : TimelineEventContent -sealed interface EncryptedMessage { - data class OlmV1Curve25519AesSha2( - val senderKey: String - ) : EncryptedMessage +data class UnableToDecryptContent( + val data: Data +) : TimelineEventContent { + sealed interface Data { + data class OlmV1Curve25519AesSha2( + val senderKey: String + ) : Data - data class MegolmV1AesSha2( - val sessionId: String - ) : EncryptedMessage + data class MegolmV1AesSha2( + val sessionId: String + ) : Data - object Unknown : EncryptedMessage + object Unknown : Data + } } -data class UnableToDecryptContent( - val message: EncryptedMessage -) : TimelineEventContent - -data class RoomMembership( +data class RoomMembershipContent( val userId: UserId, val change: MembershipChange? ) : TimelineEventContent -data class ProfileChange( +data class ProfileChangeContent( val displayName: String?, val prevDisplayName: String?, val avatarUrl: String?, val prevAvatarUrl: String? ) : TimelineEventContent -data class State( +data class StateContent( val stateKey: String, val content: OtherState ) : TimelineEventContent -data class FailedToParseMessageLike( +data class FailedToParseMessageLikeContent( val eventType: String, val error: String ) : TimelineEventContent -data class FailedToParseState( +data class FailedToParseStateContent( val eventType: String, val stateKey: String, val error: String @@ -85,9 +85,9 @@ data class FailedToParseState( object UnknownContent : TimelineEventContent -sealed interface MessageContent +sealed interface MessageType -object UnknownMessageContent : MessageContent +object UnknownMessageType : MessageType enum class MessageFormat { HTML, UNKNOWN @@ -98,44 +98,44 @@ data class FormattedBody( val body: String ) -data class EmoteMessageContent( +data class EmoteMessageType( val body: String, val formatted: FormattedBody? -) : MessageContent +) : MessageType -data class ImageMessageContent( +data class ImageMessageType( val body: String, val url: String, val info: ImageInfo? -) : MessageContent +) : MessageType -data class AudioMessageContent( +data class AudioMessageType( var body: String, var url: String, var info: AudioInfo? -) : MessageContent +) : MessageType -data class VideoMessageContent( +data class VideoMessageType( val body: String, val url: String, val info: VideoInfo? -) : MessageContent +) : MessageType -data class FileMessageContent( +data class FileMessageType( val body: String, val url: String, val info: FileInfo? -) : MessageContent +) : MessageType -data class NoticeMessageContent( +data class NoticeMessageType( val body: String, val formatted: FormattedBody? -) : MessageContent +) : MessageType -data class TextMessageContent( +data class TextMessageType( val body: String, val formatted: FormattedBody? -) : MessageContent +) : MessageType enum class MembershipChange { NONE, diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts index 49f18be870..4c0e4c6402 100644 --- a/libraries/matrix/impl/build.gradle.kts +++ b/libraries/matrix/impl/build.gradle.kts @@ -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) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 2539df41e4..5cda9a9f8d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -32,12 +32,12 @@ 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 @@ -181,9 +181,9 @@ class RustMatrixClient constructor( } catch (failure: Throwable) { Timber.e(failure, "Fail to call logout on HS. Still delete local files.") } - client.destroy() baseDirectory.deleteSessionDirectory(userID = client.userId()) sessionStore.removeSession(client.userId()) + client.destroy() } override suspend fun loadUserDisplayName(): Result = withContext(dispatchers.io) { @@ -199,23 +199,30 @@ class RustMatrixClient constructor( } @OptIn(ExperimentalUnsignedTypes::class) - override suspend fun loadMediaContentForSource(source: MediaSource): Result = + override suspend fun loadMediaContent(url: String): Result = 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 = 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() + } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt new file mode 100644 index 0000000000..96e3a367cb --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt @@ -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 + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt new file mode 100644 index 0000000000..e2f3cfe676 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt @@ -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() + ) +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 6a52217899..55361be4b4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -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 @@ -39,7 +39,6 @@ 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 @@ -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 = 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 = currentHomeserver - override suspend fun setHomeserver(homeserver: String) { + override suspend fun setHomeserver(homeserver: String): Result = 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 = 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 { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaResolver.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaResolver.kt index 8b16b8e551..68ed48350e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaResolver.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaResolver.kt @@ -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.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 - return mediaSourceFromUrl(url).use { mediaSource -> - resolve(mediaSource, kind) - } - } - - private suspend fun resolve(mediaSource: MediaSource, kind: MediaResolver.Kind): ByteArray? { return when (kind) { - is MediaResolver.Kind.Content -> client.loadMediaContentForSource(mediaSource) - is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnailForSource( - mediaSource, + is MediaResolver.Kind.Content -> client.loadMediaContent(url) + is MediaResolver.Kind.Thumbnail -> client.loadMediaThumbnail( + url, kind.width.toLong(), kind.height.toLong() ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt index 6cf2e7878f..801060171c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt @@ -17,17 +17,17 @@ 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.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.ImageMessageContent +import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat -import io.element.android.libraries.matrix.api.timeline.item.event.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.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 @@ -38,40 +38,40 @@ import org.matrix.rustcomponents.sdk.MessageFormat as RustMessageFormat class EventMessageMapper { - fun map(message: Message): TimelineEventMessageContent = message.use { + fun map(message: Message): MessageContent = 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()) + AudioMessageType(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()) + FileMessageType(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()) + ImageMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map()) } is MessageType.Notice -> { - NoticeMessageContent(type.content.body, type.content.formatted?.map()) + NoticeMessageType(type.content.body, type.content.formatted?.map()) } is MessageType.Text -> { - TextMessageContent(type.content.body, type.content.formatted?.map()) + TextMessageType(type.content.body, type.content.formatted?.map()) } is MessageType.Emote -> { - EmoteMessageContent(type.content.body, type.content.formatted?.map()) + EmoteMessageType(type.content.body, type.content.formatted?.map()) } is MessageType.Video -> { - VideoMessageContent(type.content.body, type.content.source.useUrl(), type.content.info?.map()) + VideoMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map()) } null -> { - UnknownMessageContent + UnknownMessageType } } } - TimelineEventMessageContent( + MessageContent( body = message.body(), inReplyTo = message.inReplyTo()?.let { UserId(it) }, isEdited = message.isEdited(), - content = content + type = content ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index e40e7f82d5..08a09c602f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -17,31 +17,33 @@ 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.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.ProfileChange +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent -import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembership +import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent import io.element.android.libraries.matrix.api.timeline.item.event.TimelineEventContent +import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent import io.element.android.libraries.matrix.impl.media.map import org.matrix.rustcomponents.sdk.TimelineItemContent import org.matrix.rustcomponents.sdk.TimelineItemContentKind +import org.matrix.rustcomponents.sdk.EncryptedMessage as RustEncryptedMessage class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMapper = EventMessageMapper()) { fun map(content: TimelineItemContent): TimelineEventContent = content.use { when (val kind = content.kind()) { is TimelineItemContentKind.FailedToParseMessageLike -> { - FailedToParseMessageLike( + FailedToParseMessageLikeContent( eventType = kind.eventType, error = kind.error ) } is TimelineItemContentKind.FailedToParseState -> { - FailedToParseState( + FailedToParseStateContent( eventType = kind.eventType, stateKey = kind.stateKey, error = kind.error @@ -56,7 +58,7 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap } } is TimelineItemContentKind.ProfileChange -> { - ProfileChange( + ProfileChangeContent( displayName = kind.displayName, prevDisplayName = kind.prevDisplayName, avatarUrl = kind.avatarUrl, @@ -67,7 +69,7 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap RedactedContent } is TimelineItemContentKind.RoomMembership -> { - RoomMembership( + RoomMembershipContent( UserId(kind.userId), MembershipChange.JOINED ) @@ -83,8 +85,18 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap ) } is TimelineItemContentKind.UnableToDecrypt -> { - UnknownContent + UnableToDecryptContent( + data = kind.msg.map() + ) } } } } + +private fun RustEncryptedMessage.map(): UnableToDecryptContent.Data { + return when (this) { + is RustEncryptedMessage.MegolmV1AesSha2 -> UnableToDecryptContent.Data.MegolmV1AesSha2(sessionId) + is RustEncryptedMessage.OlmV1Curve25519AesSha2 -> UnableToDecryptContent.Data.OlmV1Curve25519AesSha2(senderKey) + RustEncryptedMessage.Unknown -> UnableToDecryptContent.Data.Unknown + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 7bdaec6c0b..eb1010df87 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -26,7 +26,6 @@ import io.element.android.libraries.matrix.test.media.FakeMediaResolver import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource import kotlinx.coroutines.delay -import org.matrix.rustcomponents.sdk.MediaSource class FakeMatrixClient( override val sessionId: SessionId = A_SESSION_ID, @@ -66,11 +65,11 @@ class FakeMatrixClient( return userAvatarURLString } - override suspend fun loadMediaContentForSource(source: MediaSource): Result { + override suspend fun loadMediaContent(url: String): Result { return Result.success(ByteArray(0)) } - override suspend fun loadMediaThumbnailForSource(source: MediaSource, width: Long, height: Long): Result { + override suspend fun loadMediaThumbnail(url: String, width: Long, height: Long): Result { return Result.success(ByteArray(0)) } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt index 102ec2d868..e6a6be8012 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt @@ -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 { + return Result.failure(IllegalStateException()) } override fun getHomeserverDetails(): StateFlow { @@ -53,15 +52,16 @@ class FakeAuthenticationService : MatrixAuthenticationService { this.homeserver.value = homeserver } - override suspend fun setHomeserver(homeserver: String) { + override suspend fun setHomeserver(homeserver: String): Result { 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 { delay(100) loginError?.let { throw it } - return A_USER_ID + return Result.success(A_USER_ID) } fun givenLoginError(throwable: Throwable?) { diff --git a/samples/minimal/build.gradle.kts b/samples/minimal/build.gradle.kts index 47f5675a07..6e2a1ff5aa 100644 --- a/samples/minimal/build.gradle.kts +++ b/samples/minimal/build.gradle.kts @@ -57,5 +57,6 @@ dependencies { implementation(projects.features.roomlist.impl) implementation(projects.features.login.impl) implementation(libs.coroutines.core) + implementation(libs.matrix.sdk) coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.2") } diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt index 4ba66c51d6..53d4999934 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt @@ -72,7 +72,7 @@ class MainActivity : ComponentActivity() { } else { val matrixClient = runBlocking { val sessionId = matrixAuthenticationService.getLatestSessionId()!! - matrixAuthenticationService.restoreSession(sessionId) + matrixAuthenticationService.restoreSession(sessionId).getOrNull() } RoomListScreen(matrixClient = matrixClient!!).Content(modifier) } From dc8cfcdb8efd0446a8b8ecd651ed7bc08c4763a9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 13 Mar 2023 21:12:42 +0100 Subject: [PATCH 06/10] [MatrixSDK] make tests passes --- .../features/login/impl/error/ErrorFormatterTests.kt | 2 +- .../messages/actionlist/ActionListPresenterTest.kt | 2 +- .../android/features/messages/fixtures/aMessageEvent.kt | 2 +- .../libraries/matrix/api/auth/AuthErrorCodeTests.kt | 1 - .../matrix/test/auth/FakeAuthenticationService.kt | 9 +++++++-- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/error/ErrorFormatterTests.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/error/ErrorFormatterTests.kt index ef0c1f6da6..25d3707f68 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/error/ErrorFormatterTests.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/error/ErrorFormatterTests.kt @@ -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 { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt index 7bb9583f05..410f7186aa 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt @@ -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, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt index 08720eac06..a7208cb8af 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt @@ -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, diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCodeTests.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCodeTests.kt index c163a18766..b3ccf5264d 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCodeTests.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCodeTests.kt @@ -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 { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt index e6a6be8012..e44a1d18a8 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeAuthenticationService.kt @@ -60,8 +60,13 @@ class FakeAuthenticationService : MatrixAuthenticationService { override suspend fun login(username: String, password: String): Result { delay(100) - loginError?.let { throw it } - return Result.success(A_USER_ID) + return loginError.let { loginError -> + if (loginError == null) { + Result.success(A_USER_ID) + } else { + Result.failure(loginError) + } + } } fun givenLoginError(throwable: Throwable?) { From 76aefaf9953c1f1a51682f82e507c357f38b8429 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 14 Mar 2023 12:04:53 +0100 Subject: [PATCH 07/10] [MatrixSDK] remove Rust AuthenticationService from the dependency graph --- app/build.gradle.kts | 1 - .../auth/RustMatrixAuthenticationService.kt | 4 +-- .../libraries/matrix/impl/di/MatrixModule.kt | 36 ------------------- samples/minimal/build.gradle.kts | 1 - .../android/samples/minimal/MainActivity.kt | 2 -- 5 files changed, 2 insertions(+), 42 deletions(-) delete mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/MatrixModule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e71b20a051..e1bddd561b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -219,7 +219,6 @@ dependencies { implementation(libs.androidx.activity.compose) implementation(libs.androidx.startup) implementation(libs.coil) - implementation(libs.matrix.sdk) implementation(libs.dagger) kapt(libs.dagger.compiler) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 55361be4b4..b57af30bf3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -34,13 +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 java.io.File import javax.inject.Inject +import org.matrix.rustcomponents.sdk.AuthenticationService as RustAuthenticationService @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) @@ -49,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(null) override fun isLoggedIn(): Flow { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/MatrixModule.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/MatrixModule.kt deleted file mode 100644 index c6919e78d5..0000000000 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/MatrixModule.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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.di - -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) - } -} diff --git a/samples/minimal/build.gradle.kts b/samples/minimal/build.gradle.kts index 6e2a1ff5aa..47f5675a07 100644 --- a/samples/minimal/build.gradle.kts +++ b/samples/minimal/build.gradle.kts @@ -57,6 +57,5 @@ dependencies { implementation(projects.features.roomlist.impl) implementation(projects.features.login.impl) implementation(libs.coroutines.core) - implementation(libs.matrix.sdk) coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.2") } diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt index 53d4999934..cbd0e9839e 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt @@ -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), ) } From 7599b5617fc49a077f117ac25d1f0a4019b826f1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 14 Mar 2023 12:28:46 +0100 Subject: [PATCH 08/10] [MatrixSDK] map the remaining EventContents --- .../event/TimelineItemContentFactory.kt | 4 +- .../libraries/core/extensions/Result.kt | 2 +- ...imelineEventContent.kt => EventContent.kt} | 24 +++---- .../timeline/item/event/EventTimelineItem.kt | 2 +- .../timeline/item/event/EventMessageMapper.kt | 4 +- .../item/event/EventTimelineItemMapper.kt | 1 - .../item/event/TimelineEventContentMapper.kt | 64 +++++++++++++++++-- 7 files changed, 78 insertions(+), 23 deletions(-) rename libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/{TimelineEventContent.kt => EventContent.kt} (92%) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt index 262ef31d2e..527de007b1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt @@ -26,7 +26,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.RedactedConte import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent -import io.element.android.libraries.matrix.api.timeline.item.event.TimelineEventContent +import io.element.android.libraries.matrix.api.timeline.item.event.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 @@ -43,7 +43,7 @@ class TimelineItemContentFactory @Inject constructor( private val failedToParseStateFactory: TimelineItemContentFailedToParseStateFactory ) { - fun create(itemContent: TimelineEventContent): TimelineItemEventContent { + fun create(itemContent: EventContent): TimelineItemEventContent { return when (itemContent) { is FailedToParseMessageLikeContent -> failedToParseMessageFactory.create(itemContent) is FailedToParseStateContent -> failedToParseStateFactory.create(itemContent) diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt index b63efa781d..baa3b35e2b 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt @@ -17,7 +17,7 @@ package io.element.android.libraries.core.extensions /** - * Can be used to transform some Throwable into some other + * Can be used to transform some Throwable into some other. */ inline fun Result.mapFailure(transform: (exception: Throwable) -> Throwable): Result { return when (val exception = exceptionOrNull()) { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineEventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt similarity index 92% rename from libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineEventContent.kt rename to libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt index 12bd98568a..e19cbf9b57 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineEventContent.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt @@ -22,26 +22,26 @@ 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 +sealed interface EventContent data class MessageContent( val body: String, val inReplyTo: UserId?, val isEdited: Boolean, val type: MessageType? -) : TimelineEventContent +) : EventContent -object RedactedContent : TimelineEventContent +object RedactedContent : EventContent data class StickerContent( val body: String, val info: ImageInfo, val url: String -) : TimelineEventContent +) : EventContent data class UnableToDecryptContent( val data: Data -) : TimelineEventContent { +) : EventContent { sealed interface Data { data class OlmV1Curve25519AesSha2( val senderKey: String @@ -58,32 +58,32 @@ data class UnableToDecryptContent( data class RoomMembershipContent( val userId: UserId, val change: MembershipChange? -) : TimelineEventContent +) : EventContent data class ProfileChangeContent( val displayName: String?, val prevDisplayName: String?, val avatarUrl: String?, val prevAvatarUrl: String? -) : TimelineEventContent +) : EventContent data class StateContent( val stateKey: String, val content: OtherState -) : TimelineEventContent +) : EventContent data class FailedToParseMessageLikeContent( val eventType: String, val error: String -) : TimelineEventContent +) : EventContent data class FailedToParseStateContent( val eventType: String, val stateKey: String, val error: String -) : TimelineEventContent +) : EventContent -object UnknownContent : TimelineEventContent +object UnknownContent : EventContent sealed interface MessageType @@ -199,7 +199,7 @@ sealed interface OtherState { object RoomTombstone : OtherState data class RoomTopic( - val `topic`: String? + val topic: String? ) : OtherState object SpaceChild : OtherState diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt index 1178e9a786..8b667107d7 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt @@ -31,5 +31,5 @@ data class EventTimelineItem( val sender: UserId, val senderProfile: ProfileTimelineDetails, val timestamp: Long, - val content: TimelineEventContent + val content: EventContent ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt index 801060171c..13db63a261 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt @@ -39,7 +39,7 @@ import org.matrix.rustcomponents.sdk.MessageFormat as RustMessageFormat class EventMessageMapper { fun map(message: Message): MessageContent = message.use { - val content = message.msgtype().use { type -> + val type = message.msgtype().use { type -> when (type) { is MessageType.Audio -> { AudioMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map()) @@ -71,7 +71,7 @@ class EventMessageMapper { body = message.body(), inReplyTo = message.inReplyTo()?.let { UserId(it) }, isEdited = message.isEdited(), - type = content + type = type ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index c399dd0b1d..11e60eea0e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -22,7 +22,6 @@ 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 diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index 08a09c602f..51e84b441f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -20,21 +20,25 @@ 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.TimelineEventContent +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): TimelineEventContent = content.use { + fun map(content: TimelineItemContent): EventContent = content.use { when (val kind = content.kind()) { is TimelineItemContentKind.FailedToParseMessageLike -> { FailedToParseMessageLikeContent( @@ -71,11 +75,14 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap is TimelineItemContentKind.RoomMembership -> { RoomMembershipContent( UserId(kind.userId), - MembershipChange.JOINED + kind.change?.map() ) } is TimelineItemContentKind.State -> { - UnknownContent + StateContent( + stateKey = kind.stateKey, + content = kind.content.map() + ) } is TimelineItemContentKind.Sticker -> { StickerContent( @@ -93,6 +100,55 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap } } +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) From 3bbb857a198d075aa129c7a29ded177d6bbd9fee Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 14 Mar 2023 16:26:11 +0100 Subject: [PATCH 09/10] Adapt kover rules. --- build.gradle.kts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2f7ebd91d2..f9ba17349b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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 From 5ba4c2618e90a9579fd9fa25acfb719f526aaec9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 14 Mar 2023 17:06:01 +0100 Subject: [PATCH 10/10] [Login] remove trim on password --- .../android/features/login/impl/root/LoginRootPresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootPresenter.kt index 46a42ad24c..c9a75365f1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootPresenter.kt @@ -71,7 +71,7 @@ class LoginRootPresenter @Inject constructor(private val authenticationService: loggedInState.value = LoggedInState.LoggingIn //TODO rework the setHomeserver flow authenticationService.setHomeserver(homeserver) - authenticationService.login(formState.login.trim(), formState.password.trim()) + authenticationService.login(formState.login.trim(), formState.password) .onSuccess { sessionId -> loggedInState.value = LoggedInState.LoggedIn(sessionId) }