Threads - first iteration (#5165)
* Initial threads support: parse `ThreadSummary`. Replace several `isThreaded` values with `EventThreadInfo`, which contains the info about the event either being the root of a thread or part of it. * Add `Threaded` timeline mode * Add a `liveTimeline` parameter to `TimelineController`'s constructor. This way we can customise which timeline will be used as the 'live' one. Also add `@LiveTimeline` DI qualifier for the actual live timeline of the room. * Create `ThreadedMessagesNode`. Allow opening a thread in a separate screen. * Add the callbacks for the list menu actions - even if they're the wrong ones and will send the data to the room instead * Send attachments and location in threads * Fix polls in threads, add support for sending voice messages in threads * Display thread summaries only when the feature flag is enabled * Use 'Reply' instead of 'Reply in thread' when in threaded timeline mode * Remove incorrect usage of `Timeline` in `MessageComposerPresenter`. This led to replies to threaded events not appearing as actual replies. --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
cc10ba41fd
commit
35928e3630
119 changed files with 1520 additions and 339 deletions
|
|
@ -14,6 +14,7 @@ import io.element.android.libraries.core.coroutine.childScope
|
|||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import io.element.android.libraries.core.extensions.mapFailure
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
|
|
@ -133,6 +134,7 @@ class RustMatrixClient(
|
|||
baseCacheDirectory: File,
|
||||
clock: SystemClock,
|
||||
timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
) : MatrixClient {
|
||||
override val sessionId: UserId = UserId(innerClient.userId())
|
||||
override val deviceId: DeviceId = DeviceId(innerClient.deviceId())
|
||||
|
|
@ -203,6 +205,7 @@ class RustMatrixClient(
|
|||
timelineEventTypeFilterFactory = timelineEventTypeFilterFactory,
|
||||
roomMembershipObserver = roomMembershipObserver,
|
||||
roomInfoMapper = roomInfoMapper,
|
||||
featureFlagService = featureFlagService,
|
||||
)
|
||||
|
||||
override val mediaLoader: MatrixMediaLoader = RustMediaLoader(
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
baseCacheDirectory = cacheDirectory,
|
||||
clock = clock,
|
||||
timelineEventTypeFilterFactory = timelineEventTypeFilterFactory,
|
||||
featureFlagService = featureFlagService,
|
||||
).also {
|
||||
Timber.tag(it.toString()).d("Creating Client with access token '$anonymizedAccessToken' and refresh token '$anonymizedRefreshToken'")
|
||||
}
|
||||
|
|
@ -131,6 +132,7 @@ class RustMatrixClientFactory @Inject constructor(
|
|||
)
|
||||
)
|
||||
.enableShareHistoryOnInvite(featureFlagService.isFeatureEnabled(FeatureFlags.EnableKeyShareOnInvite))
|
||||
.threadsEnabled(featureFlagService.isFeatureEnabled(FeatureFlags.HideThreadedEvents), threadSubscriptions = false)
|
||||
.run {
|
||||
// Apply sliding sync version settings
|
||||
when (slidingSyncType) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
|||
import io.element.android.libraries.core.coroutine.childScope
|
||||
import io.element.android.libraries.core.extensions.mapFailure
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
|
|
@ -83,6 +85,7 @@ class JoinedRustRoom(
|
|||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
private val systemClock: SystemClock,
|
||||
private val roomContentForwarder: RoomContentForwarder,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
) : JoinedRoom, BaseRoom by baseRoom {
|
||||
// Create a dispatcher for all room methods...
|
||||
private val roomDispatcher = coroutineDispatchers.io.limitedParallelism(32)
|
||||
|
|
@ -132,7 +135,7 @@ class JoinedRustRoom(
|
|||
|
||||
override val roomNotificationSettingsStateFlow = MutableStateFlow<RoomNotificationSettingsState>(RoomNotificationSettingsState.Unknown)
|
||||
|
||||
override val liveTimeline = liveInnerTimeline.map(mode = Timeline.Mode.LIVE) {
|
||||
override val liveTimeline = liveInnerTimeline.map(mode = Timeline.Mode.Live) {
|
||||
syncUpdateFlow.value = systemClock.epochMillis()
|
||||
}
|
||||
|
||||
|
|
@ -153,22 +156,27 @@ class JoinedRustRoom(
|
|||
override suspend fun createTimeline(
|
||||
createTimelineParams: CreateTimelineParams,
|
||||
): Result<Timeline> = withContext(roomDispatcher) {
|
||||
val hideThreadedEvents = featureFlagService.isFeatureEnabled(FeatureFlags.HideThreadedEvents)
|
||||
val focus = when (createTimelineParams) {
|
||||
is CreateTimelineParams.PinnedOnly -> TimelineFocus.PinnedEvents(
|
||||
maxEventsToLoad = 100u,
|
||||
maxConcurrentRequests = 10u,
|
||||
)
|
||||
is CreateTimelineParams.MediaOnly -> TimelineFocus.Live(hideThreadedEvents = false)
|
||||
is CreateTimelineParams.MediaOnly -> TimelineFocus.Live(hideThreadedEvents = hideThreadedEvents)
|
||||
is CreateTimelineParams.Focused -> TimelineFocus.Event(
|
||||
eventId = createTimelineParams.focusedEventId.value,
|
||||
numContextEvents = 50u,
|
||||
hideThreadedEvents = false,
|
||||
hideThreadedEvents = hideThreadedEvents,
|
||||
)
|
||||
is CreateTimelineParams.MediaOnlyFocused -> TimelineFocus.Event(
|
||||
eventId = createTimelineParams.focusedEventId.value,
|
||||
numContextEvents = 50u,
|
||||
// Never hide threaded events in media focused timeline
|
||||
hideThreadedEvents = false,
|
||||
)
|
||||
is CreateTimelineParams.Threaded -> TimelineFocus.Thread(
|
||||
rootEventId = createTimelineParams.threadRootEventId.value,
|
||||
)
|
||||
}
|
||||
|
||||
val filter = when (createTimelineParams) {
|
||||
|
|
@ -182,7 +190,8 @@ class JoinedRustRoom(
|
|||
)
|
||||
)
|
||||
is CreateTimelineParams.Focused,
|
||||
CreateTimelineParams.PinnedOnly -> TimelineFilter.All
|
||||
CreateTimelineParams.PinnedOnly,
|
||||
is CreateTimelineParams.Threaded -> TimelineFilter.All
|
||||
}
|
||||
|
||||
val internalIdPrefix = when (createTimelineParams) {
|
||||
|
|
@ -190,6 +199,7 @@ class JoinedRustRoom(
|
|||
is CreateTimelineParams.Focused -> "focus_${createTimelineParams.focusedEventId}"
|
||||
is CreateTimelineParams.MediaOnly -> "MediaGallery_"
|
||||
is CreateTimelineParams.MediaOnlyFocused -> "MediaGallery_${createTimelineParams.focusedEventId}"
|
||||
is CreateTimelineParams.Threaded -> "Thread_${createTimelineParams.threadRootEventId}"
|
||||
}
|
||||
|
||||
// Note that for TimelineFilter.MediaOnlyFocused, the date separator will be filtered out,
|
||||
|
|
@ -198,7 +208,8 @@ class JoinedRustRoom(
|
|||
is CreateTimelineParams.MediaOnly,
|
||||
is CreateTimelineParams.MediaOnlyFocused -> DateDividerMode.MONTHLY
|
||||
is CreateTimelineParams.Focused,
|
||||
CreateTimelineParams.PinnedOnly -> DateDividerMode.DAILY
|
||||
CreateTimelineParams.PinnedOnly,
|
||||
is CreateTimelineParams.Threaded -> DateDividerMode.DAILY
|
||||
}
|
||||
|
||||
// Track read receipts only for focused timeline for performance optimization
|
||||
|
|
@ -216,17 +227,19 @@ class JoinedRustRoom(
|
|||
)
|
||||
).let { innerTimeline ->
|
||||
val mode = when (createTimelineParams) {
|
||||
is CreateTimelineParams.Focused -> Timeline.Mode.FOCUSED_ON_EVENT
|
||||
is CreateTimelineParams.MediaOnly -> Timeline.Mode.MEDIA
|
||||
is CreateTimelineParams.MediaOnlyFocused -> Timeline.Mode.FOCUSED_ON_EVENT
|
||||
CreateTimelineParams.PinnedOnly -> Timeline.Mode.PINNED_EVENTS
|
||||
is CreateTimelineParams.Focused -> Timeline.Mode.FocusedOnEvent(createTimelineParams.focusedEventId)
|
||||
is CreateTimelineParams.MediaOnly -> Timeline.Mode.Media
|
||||
is CreateTimelineParams.MediaOnlyFocused -> Timeline.Mode.FocusedOnEvent(createTimelineParams.focusedEventId)
|
||||
CreateTimelineParams.PinnedOnly -> Timeline.Mode.PinnedEvents
|
||||
is CreateTimelineParams.Threaded -> Timeline.Mode.Thread(createTimelineParams.threadRootEventId)
|
||||
}
|
||||
innerTimeline.map(mode = mode)
|
||||
}
|
||||
}.mapFailure {
|
||||
when (createTimelineParams) {
|
||||
is CreateTimelineParams.Focused,
|
||||
is CreateTimelineParams.MediaOnlyFocused -> it.toFocusEventException()
|
||||
is CreateTimelineParams.MediaOnlyFocused,
|
||||
is CreateTimelineParams.Threaded -> it.toFocusEventException()
|
||||
CreateTimelineParams.MediaOnly,
|
||||
CreateTimelineParams.PinnedOnly -> it
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ package io.element.android.libraries.matrix.impl.room
|
|||
|
||||
import io.element.android.appconfig.TimelineConfig
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
|
@ -48,6 +50,7 @@ class RustRoomFactory(
|
|||
private val innerRoomListService: InnerRoomListService,
|
||||
private val roomSyncSubscriber: RoomSyncSubscriber,
|
||||
private val timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val roomMembershipObserver: RoomMembershipObserver,
|
||||
private val roomInfoMapper: RoomInfoMapper,
|
||||
) {
|
||||
|
|
@ -105,10 +108,11 @@ class RustRoomFactory(
|
|||
val sdkRoom = awaitRoomInRoomList(roomId) ?: return@withContext null
|
||||
|
||||
if (sdkRoom.membership() == Membership.JOINED) {
|
||||
val hideThreadedEvents = featureFlagService.isFeatureEnabled(FeatureFlags.HideThreadedEvents)
|
||||
// Init the live timeline in the SDK from the Room
|
||||
val timeline = sdkRoom.timelineWithConfiguration(
|
||||
TimelineConfiguration(
|
||||
focus = TimelineFocus.Live(hideThreadedEvents = false),
|
||||
focus = TimelineFocus.Live(hideThreadedEvents = hideThreadedEvents),
|
||||
filter = eventFilters?.let(TimelineFilter::EventTypeFilter) ?: TimelineFilter.All,
|
||||
internalIdPrefix = "live",
|
||||
dateDividerMode = DateDividerMode.DAILY,
|
||||
|
|
@ -125,6 +129,7 @@ class RustRoomFactory(
|
|||
liveInnerTimeline = timeline,
|
||||
coroutineDispatchers = dispatchers,
|
||||
systemClock = systemClock,
|
||||
featureFlagService = featureFlagService,
|
||||
)
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ private const val PAGINATION_SIZE = 50
|
|||
|
||||
class RustTimeline(
|
||||
private val inner: InnerTimeline,
|
||||
mode: Timeline.Mode,
|
||||
override val mode: Timeline.Mode,
|
||||
systemClock: SystemClock,
|
||||
private val joinedRoom: JoinedRoom,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
|
|
@ -118,19 +118,20 @@ class RustTimeline(
|
|||
private val typingNotificationPostProcessor = TypingNotificationPostProcessor(mode)
|
||||
|
||||
override val backwardPaginationStatus = MutableStateFlow(
|
||||
Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode != Timeline.Mode.PINNED_EVENTS)
|
||||
Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode != Timeline.Mode.PinnedEvents)
|
||||
)
|
||||
|
||||
override val forwardPaginationStatus = MutableStateFlow(
|
||||
Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode == Timeline.Mode.FOCUSED_ON_EVENT)
|
||||
Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode !is Timeline.Mode.FocusedOnEvent)
|
||||
)
|
||||
|
||||
init {
|
||||
if (mode != Timeline.Mode.PINNED_EVENTS) {
|
||||
coroutineScope.fetchMembers()
|
||||
when (mode) {
|
||||
is Timeline.Mode.Live, is Timeline.Mode.FocusedOnEvent -> coroutineScope.fetchMembers()
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
if (mode == Timeline.Mode.LIVE) {
|
||||
if (mode == Timeline.Mode.Live) {
|
||||
// When timeline is live, we need to listen to the back pagination status as
|
||||
// sdk can automatically paginate backwards.
|
||||
coroutineScope.registerBackPaginationStatusListener()
|
||||
|
|
@ -219,6 +220,7 @@ class RustTimeline(
|
|||
items = items,
|
||||
hasMoreToLoadBackward = backwardPaginationStatus.hasMoreToLoad,
|
||||
hasMoreToLoadForward = forwardPaginationStatus.hasMoreToLoad,
|
||||
timelineMode = mode,
|
||||
)
|
||||
}
|
||||
.let { items ->
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.timeline.item.event
|
||||
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
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
|
||||
|
|
@ -37,14 +38,14 @@ private const val MSG_TYPE_GALLERY_UNSTABLE = "dm.filament.gallery"
|
|||
class EventMessageMapper {
|
||||
private val inReplyToMapper by lazy { InReplyToMapper(TimelineEventContentMapper()) }
|
||||
|
||||
fun map(message: MsgLikeKind.Message, inReplyTo: InReplyToDetails?, isThreaded: Boolean): MessageContent = message.use {
|
||||
fun map(message: MsgLikeKind.Message, inReplyTo: InReplyToDetails?, threadInfo: EventThreadInfo): MessageContent = message.use {
|
||||
val type = it.content.msgType.use(this::mapMessageType)
|
||||
val inReplyToEvent: InReplyTo? = inReplyTo?.use(inReplyToMapper::map)
|
||||
MessageContent(
|
||||
body = it.content.body,
|
||||
inReplyTo = inReplyToEvent,
|
||||
isEdited = it.content.isEdited,
|
||||
isThreaded = isThreaded,
|
||||
threadInfo = threadInfo,
|
||||
type = type
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
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.TransactionId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import org.matrix.rustcomponents.sdk.EventOrTransactionId as RustEventOrTransactionId
|
||||
|
||||
fun RustEventOrTransactionId.map(): EventOrTransactionId = when (this) {
|
||||
is RustEventOrTransactionId.EventId -> EventOrTransactionId.Event(EventId(eventId))
|
||||
is RustEventOrTransactionId.TransactionId -> EventOrTransactionId.Transaction(TransactionId(transactionId))
|
||||
}
|
||||
|
|
@ -7,7 +7,12 @@
|
|||
|
||||
package io.element.android.libraries.matrix.impl.timeline.item.event
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EmbeddedEventInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.ThreadSummary
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
|
||||
|
|
@ -27,6 +32,7 @@ import io.element.android.libraries.matrix.impl.media.map
|
|||
import io.element.android.libraries.matrix.impl.poll.map
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import org.matrix.rustcomponents.sdk.EmbeddedEventDetails
|
||||
import org.matrix.rustcomponents.sdk.MsgLikeKind
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContent
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
|
|
@ -59,8 +65,35 @@ class TimelineEventContentMapper(
|
|||
when (val kind = it.content.kind) {
|
||||
is MsgLikeKind.Message -> {
|
||||
val inReplyTo = it.content.inReplyTo
|
||||
val isThreaded = it.content.threadRoot != null
|
||||
eventMessageMapper.map(kind, inReplyTo, isThreaded)
|
||||
val threadSummary = it.content.threadSummary?.use { summary ->
|
||||
val numberOfReplies = summary.numReplies().toLong()
|
||||
val latestEvent = summary.latestEvent()
|
||||
val details = when (latestEvent) {
|
||||
is EmbeddedEventDetails.Unavailable -> AsyncData.Uninitialized
|
||||
is EmbeddedEventDetails.Pending -> AsyncData.Loading()
|
||||
is EmbeddedEventDetails.Error -> AsyncData.Failure(IllegalStateException(latestEvent.message))
|
||||
is EmbeddedEventDetails.Ready -> {
|
||||
AsyncData.Success(
|
||||
EmbeddedEventInfo(
|
||||
eventOrTransactionId = latestEvent.eventOrTransactionId.map(),
|
||||
content = map(latestEvent.content),
|
||||
senderId = UserId(latestEvent.sender),
|
||||
senderProfile = latestEvent.senderProfile.map(),
|
||||
timestamp = latestEvent.timestamp.toLong()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
ThreadSummary(
|
||||
latestEvent = details,
|
||||
numberOfReplies = numberOfReplies,
|
||||
)
|
||||
}
|
||||
val threadInfo = EventThreadInfo(
|
||||
threadRootId = it.content.threadRoot?.let(::ThreadId),
|
||||
threadSummary = threadSummary,
|
||||
)
|
||||
eventMessageMapper.map(kind, inReplyTo, threadInfo)
|
||||
}
|
||||
is MsgLikeKind.Redacted -> {
|
||||
RedactedContent
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class LastForwardIndicatorsPostProcessor(
|
|||
items: List<MatrixTimelineItem>,
|
||||
): List<MatrixTimelineItem> {
|
||||
// We don't need to add the last forward indicator if we are not in the FOCUSED_ON_EVENT mode
|
||||
if (mode != Timeline.Mode.FOCUSED_ON_EVENT) {
|
||||
if (mode !is Timeline.Mode.FocusedOnEvent) {
|
||||
return items
|
||||
} else {
|
||||
return buildList {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,9 @@ class LoadingIndicatorsPostProcessor(private val systemClock: SystemClock) {
|
|||
items: List<MatrixTimelineItem>,
|
||||
hasMoreToLoadBackward: Boolean,
|
||||
hasMoreToLoadForward: Boolean,
|
||||
timelineMode: Timeline.Mode,
|
||||
): List<MatrixTimelineItem> {
|
||||
val shouldAddForwardLoadingIndicator = hasMoreToLoadForward && items.isNotEmpty()
|
||||
val shouldAddForwardLoadingIndicator = timelineMode is Timeline.Mode.Live && hasMoreToLoadForward && items.isNotEmpty()
|
||||
val currentTimestamp = systemClock.epochMillis()
|
||||
return buildList {
|
||||
if (hasMoreToLoadBackward) {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class RoomBeginningPostProcessor(private val mode: Timeline.Mode) {
|
|||
): List<MatrixTimelineItem> {
|
||||
return when {
|
||||
items.isEmpty() -> items
|
||||
mode == Timeline.Mode.PINNED_EVENTS -> items
|
||||
mode == Timeline.Mode.PinnedEvents -> items
|
||||
isDm -> processForDM(items, roomCreator)
|
||||
hasMoreToLoadBackwards -> items
|
||||
else -> processForRoom(items)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTime
|
|||
*/
|
||||
class TypingNotificationPostProcessor(private val mode: Timeline.Mode) {
|
||||
fun process(items: List<MatrixTimelineItem>): List<MatrixTimelineItem> {
|
||||
return if (mode == Timeline.Mode.LIVE) {
|
||||
return if (mode is Timeline.Mode.Live) {
|
||||
buildList {
|
||||
addAll(items)
|
||||
add(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.libraries.matrix.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClient
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiSyncService
|
||||
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventTypeFilterFactory
|
||||
|
|
@ -66,5 +67,6 @@ class RustMatrixClientTest {
|
|||
baseCacheDirectory = File(""),
|
||||
clock = FakeSystemClock(),
|
||||
timelineEventTypeFilterFactory = FakeTimelineEventTypeFilterFactory(),
|
||||
featureFlagService = FakeFeatureFlagService(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ class FakeFfiClientBuilder : ClientBuilder(NoPointer) {
|
|||
override fun userAgent(userAgent: String) = this
|
||||
override fun username(username: String) = this
|
||||
override fun enableShareHistoryOnInvite(enableShareHistoryOnInvite: Boolean): ClientBuilder = this
|
||||
override fun threadsEnabled(enabled: Boolean, threadSubscriptions: Boolean): ClientBuilder = this
|
||||
|
||||
override suspend fun build(): Client {
|
||||
return FakeFfiClient(withUtdHook = {})
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class RustTimelineTest {
|
|||
|
||||
private fun TestScope.createRustTimeline(
|
||||
inner: InnerTimeline,
|
||||
mode: Timeline.Mode = Timeline.Mode.LIVE,
|
||||
mode: Timeline.Mode = Timeline.Mode.Live,
|
||||
systemClock: SystemClock = FakeSystemClock(),
|
||||
joinedRoom: JoinedRoom = FakeJoinedRoom().apply { givenRoomInfo(aRoomInfo()) },
|
||||
coroutineScope: CoroutineScope = backgroundScope,
|
||||
|
|
|
|||
|
|
@ -12,19 +12,20 @@ import io.element.android.libraries.matrix.api.core.UniqueId
|
|||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import org.junit.Test
|
||||
|
||||
class LastForwardIndicatorsPostProcessorTest {
|
||||
@Test
|
||||
fun `LastForwardIndicatorsPostProcessor does not alter the items with mode not FOCUSED_ON_EVENT`() {
|
||||
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.LIVE)
|
||||
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.Live)
|
||||
val result = sut.process(listOf(messageEvent))
|
||||
assertThat(result).containsExactly(messageEvent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `LastForwardIndicatorsPostProcessor add virtual items`() {
|
||||
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.FOCUSED_ON_EVENT)
|
||||
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.FocusedOnEvent(AN_EVENT_ID))
|
||||
val result = sut.process(listOf(messageEvent))
|
||||
assertThat(result).containsExactly(
|
||||
messageEvent,
|
||||
|
|
@ -37,7 +38,7 @@ class LastForwardIndicatorsPostProcessorTest {
|
|||
|
||||
@Test
|
||||
fun `LastForwardIndicatorsPostProcessor add virtual items on empty list`() {
|
||||
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.FOCUSED_ON_EVENT)
|
||||
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.FocusedOnEvent(AN_EVENT_ID))
|
||||
val result = sut.process(listOf())
|
||||
assertThat(result).containsExactly(
|
||||
MatrixTimelineItem.Virtual(
|
||||
|
|
@ -49,7 +50,7 @@ class LastForwardIndicatorsPostProcessorTest {
|
|||
|
||||
@Test
|
||||
fun `LastForwardIndicatorsPostProcessor add virtual items but does not alter the list if called a second time`() {
|
||||
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.FOCUSED_ON_EVENT)
|
||||
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.FocusedOnEvent(AN_EVENT_ID))
|
||||
// Process a first time
|
||||
sut.process(listOf(messageEvent))
|
||||
// Process a second time with the same Event
|
||||
|
|
@ -65,7 +66,7 @@ class LastForwardIndicatorsPostProcessorTest {
|
|||
|
||||
@Test
|
||||
fun `LastForwardIndicatorsPostProcessor add virtual items each time it is called with new Events`() {
|
||||
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.FOCUSED_ON_EVENT)
|
||||
val sut = LastForwardIndicatorsPostProcessor(Timeline.Mode.FocusedOnEvent(AN_EVENT_ID))
|
||||
// Process a first time
|
||||
sut.process(listOf(dayEvent, messageEvent))
|
||||
// Process a second time with the same Event
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class LoadingIndicatorsPostProcessorTest {
|
|||
items = listOf(messageEvent, messageEvent2),
|
||||
hasMoreToLoadBackward = true,
|
||||
hasMoreToLoadForward = false,
|
||||
timelineMode = Timeline.Mode.Live,
|
||||
)
|
||||
assertThat(result).containsExactly(
|
||||
MatrixTimelineItem.Virtual(
|
||||
|
|
@ -46,6 +47,7 @@ class LoadingIndicatorsPostProcessorTest {
|
|||
items = listOf(messageEvent, messageEvent2),
|
||||
hasMoreToLoadBackward = false,
|
||||
hasMoreToLoadForward = true,
|
||||
timelineMode = Timeline.Mode.Live,
|
||||
)
|
||||
assertThat(result).containsExactly(
|
||||
messageEvent,
|
||||
|
|
@ -68,6 +70,7 @@ class LoadingIndicatorsPostProcessorTest {
|
|||
items = listOf(messageEvent, messageEvent2),
|
||||
hasMoreToLoadBackward = true,
|
||||
hasMoreToLoadForward = true,
|
||||
timelineMode = Timeline.Mode.Live,
|
||||
)
|
||||
assertThat(result).containsExactly(
|
||||
MatrixTimelineItem.Virtual(
|
||||
|
|
@ -97,6 +100,7 @@ class LoadingIndicatorsPostProcessorTest {
|
|||
items = listOf(),
|
||||
hasMoreToLoadBackward = true,
|
||||
hasMoreToLoadForward = true,
|
||||
timelineMode = Timeline.Mode.Live,
|
||||
)
|
||||
assertThat(result).containsExactly(
|
||||
MatrixTimelineItem.Virtual(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import org.junit.Test
|
|||
class RoomBeginningPostProcessorTest {
|
||||
@Test
|
||||
fun `processor returns empty list when empty list is provided`() {
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
|
||||
val processedItems = processor.process(
|
||||
items = emptyList(),
|
||||
isDm = true,
|
||||
|
|
@ -27,7 +27,7 @@ class RoomBeginningPostProcessorTest {
|
|||
|
||||
@Test
|
||||
fun `processor returns the provided list when it only contains a message`() {
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
|
||||
val processedItems = processor.process(
|
||||
items = listOf(messageEvent),
|
||||
isDm = true,
|
||||
|
|
@ -39,7 +39,7 @@ class RoomBeginningPostProcessorTest {
|
|||
|
||||
@Test
|
||||
fun `processor returns the provided list when it only contains a message and the roomCreator is not provided`() {
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
|
||||
val processedItems = processor.process(
|
||||
items = listOf(messageEvent),
|
||||
isDm = true,
|
||||
|
|
@ -56,7 +56,7 @@ class RoomBeginningPostProcessorTest {
|
|||
roomCreateEvent,
|
||||
roomCreatorJoinEvent,
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
|
||||
val processedItems = processor.process(
|
||||
items = timelineItems,
|
||||
isDm = true,
|
||||
|
|
@ -72,7 +72,7 @@ class RoomBeginningPostProcessorTest {
|
|||
roomCreateEvent,
|
||||
roomCreatorJoinEvent,
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.PINNED_EVENTS)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.PinnedEvents)
|
||||
val processedItems = processor.process(
|
||||
items = timelineItems,
|
||||
isDm = true,
|
||||
|
|
@ -94,7 +94,7 @@ class RoomBeginningPostProcessorTest {
|
|||
otherMemberJoinEvent,
|
||||
messageEvent,
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
|
||||
val processedItems = processor.process(timelineItems, isDm = true, roomCreator = A_USER_ID, hasMoreToLoadBackwards = false)
|
||||
assertThat(processedItems).isEqualTo(expected)
|
||||
}
|
||||
|
|
@ -105,7 +105,7 @@ class RoomBeginningPostProcessorTest {
|
|||
roomCreateEvent,
|
||||
roomCreatorJoinEvent,
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
|
||||
val processedItems = processor.process(timelineItems, isDm = true, roomCreator = A_USER_ID, hasMoreToLoadBackwards = true)
|
||||
assertThat(processedItems).isEmpty()
|
||||
}
|
||||
|
|
@ -115,7 +115,7 @@ class RoomBeginningPostProcessorTest {
|
|||
val timelineItems = listOf(
|
||||
roomCreatorJoinEvent,
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
|
||||
val processedItems = processor.process(timelineItems, isDm = true, roomCreator = A_USER_ID, hasMoreToLoadBackwards = true)
|
||||
assertThat(processedItems).isEmpty()
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@ class RoomBeginningPostProcessorTest {
|
|||
roomCreateEvent,
|
||||
otherMemberJoinEvent,
|
||||
)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
|
||||
val processor = RoomBeginningPostProcessor(Timeline.Mode.Live)
|
||||
val processedItems = processor.process(timelineItems, isDm = true, roomCreator = A_USER_ID, hasMoreToLoadBackwards = true)
|
||||
assertThat(processedItems).isEqualTo(listOf(otherMemberJoinEvent))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue