Format and cleanup files

This commit is contained in:
Benoit Marty
2024-04-26 13:57:08 +02:00
parent d20605b8be
commit 21ddeb00fe
27 changed files with 235 additions and 111 deletions

View File

@@ -153,7 +153,6 @@ val SemanticColors.bigCheckmarkBorderColor
val SemanticColors.highlightedMessageBackgroundColor
get() = if (isLight) LightColorTokens.colorGreen300 else DarkColorTokens.colorGreen300
@PreviewsDayNight
@Composable
internal fun ColorAliasesPreview() = ElementPreview {

View File

@@ -32,9 +32,8 @@ import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import kotlinx.coroutines.flow.Flow

View File

@@ -33,7 +33,6 @@ import kotlinx.coroutines.flow.StateFlow
import java.io.File
interface Timeline : AutoCloseable {
data class PaginationStatus(
val isPaginating: Boolean,
val hasMoreToLoad: Boolean,
@@ -52,7 +51,6 @@ interface Timeline : AutoCloseable {
fun paginationStatus(direction: PaginationDirection): StateFlow<PaginationStatus>
val timelineItems: Flow<List<MatrixTimelineItem>>
suspend fun sendMessage(body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit>
suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit>
@@ -164,6 +162,4 @@ interface Timeline : AutoCloseable {
waveform: List<Float>,
progressCallback: ProgressCallback?
): Result<MediaUploadHandler>
}

View File

@@ -19,11 +19,9 @@ package io.element.android.libraries.matrix.api.timeline
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.room.MatrixRoom
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import javax.inject.Inject
/**
@@ -45,4 +43,3 @@ class LiveTimelineProvider @Inject constructor(
) : TimelineProvider {
override fun activeTimelineFlow(): StateFlow<Timeline> = MutableStateFlow(room.liveTimeline)
}

View File

@@ -27,12 +27,12 @@ sealed interface VirtualTimelineItem {
data object EncryptedHistoryBanner : VirtualTimelineItem
data object RoomBeginning: VirtualTimelineItem
data object RoomBeginning : VirtualTimelineItem
data object LastForwardIndicator: VirtualTimelineItem
data object LastForwardIndicator : VirtualTimelineItem
data class LoadingIndicator(
val direction: Timeline.PaginationDirection,
val timestamp: Long,
): VirtualTimelineItem
) : VirtualTimelineItem
}

View File

@@ -47,7 +47,6 @@ import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl
import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService
import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
@@ -78,14 +77,10 @@ import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.RoomInfo
import org.matrix.rustcomponents.sdk.RoomInfoListener
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomMessageEventContentWithoutRelation
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
import org.matrix.rustcomponents.sdk.TypingNotificationsListener
import org.matrix.rustcomponents.sdk.UserPowerLevelUpdate
import org.matrix.rustcomponents.sdk.WidgetCapabilities
import org.matrix.rustcomponents.sdk.WidgetCapabilitiesProvider
import org.matrix.rustcomponents.sdk.messageEventContentFromHtml
import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown
import org.matrix.rustcomponents.sdk.use
import uniffi.matrix_sdk.RoomPowerLevelChanges
import java.io.File
@@ -151,7 +146,7 @@ class RustMatrixRoom(
private val _roomNotificationSettingsStateFlow = MutableStateFlow<MatrixRoomNotificationSettingsState>(MatrixRoomNotificationSettingsState.Unknown)
override val roomNotificationSettingsStateFlow: StateFlow<MatrixRoomNotificationSettingsState> = _roomNotificationSettingsStateFlow
override val liveTimeline = createTimeline(innerTimeline, isLive = true){
override val liveTimeline = createTimeline(innerTimeline, isLive = true) {
_syncUpdateFlow.value = systemClock.epochMillis()
}
@@ -179,7 +174,7 @@ class RustMatrixRoom(
eventId = eventId.value,
numContextEvents = 50u,
internalIdPrefix = "focus_$eventId",
).let {inner ->
).let { inner ->
createTimeline(inner, isLive = false)
}
}
@@ -322,7 +317,7 @@ class RustMatrixRoom(
}
}
override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> {
override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> {
return liveTimeline.sendMessage(body, htmlBody, mentions)
}
@@ -340,7 +335,7 @@ class RustMatrixRoom(
return liveTimeline.enterSpecialMode(eventId)
}
override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit>{
override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> {
return liveTimeline.replyMessage(eventId, body, htmlBody, mentions)
}
@@ -446,11 +441,11 @@ class RustMatrixRoom(
return liveTimeline.sendFile(file, fileInfo, progressCallback)
}
override suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit>{
override suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit> {
return liveTimeline.toggleReaction(emoji, eventId)
}
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit>{
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> {
return liveTimeline.forwardEvent(eventId, roomIds)
}
@@ -458,7 +453,7 @@ class RustMatrixRoom(
return liveTimeline.retrySendMessage(transactionId)
}
override suspend fun cancelSend(transactionId: TransactionId): Result<Unit>{
override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> {
return liveTimeline.cancelSend(transactionId)
}
@@ -563,14 +558,14 @@ class RustMatrixRoom(
override suspend fun sendPollResponse(
pollStartId: EventId,
answers: List<String>
): Result<Unit> {
): Result<Unit> {
return liveTimeline.sendPollResponse(pollStartId, answers)
}
override suspend fun endPoll(
pollStartId: EventId,
text: String
): Result<Unit> {
): Result<Unit> {
return liveTimeline.endPoll(pollStartId, text)
}
@@ -579,7 +574,7 @@ class RustMatrixRoom(
audioInfo: AudioInfo,
waveform: List<Float>,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler>{
): Result<MediaUploadHandler> {
return liveTimeline.sendVoiceMessage(file, audioInfo, waveform, progressCallback)
}
@@ -634,5 +629,4 @@ class RustMatrixRoom(
inner = timeline,
)
}
}

View File

@@ -89,17 +89,16 @@ private const val PAGINATION_SIZE = 50
class RustTimeline(
private val inner: InnerTimeline,
private val isLive: Boolean,
private val systemClock: SystemClock,
private val roomCoroutineScope: CoroutineScope,
private val isKeyBackupEnabled: Boolean,
isLive: Boolean,
systemClock: SystemClock,
roomCoroutineScope: CoroutineScope,
isKeyBackupEnabled: Boolean,
private val matrixRoom: MatrixRoom,
private val dispatcher: CoroutineDispatcher,
private val lastLoginTimestamp: Date?,
lastLoginTimestamp: Date?,
private val roomContentForwarder: RoomContentForwarder,
private val onNewSyncedEvent: () -> Unit,
) : Timeline {
private val initLatch = CompletableDeferred<Unit>()
private val isInit = AtomicBoolean(false)
@@ -173,7 +172,7 @@ class RustTimeline(
}
}
// Use NonCancellable to avoid breaking the timeline when the coroutine is cancelled.
// Use NonCancellable to avoid breaking the timeline when the coroutine is cancelled.
override suspend fun paginate(direction: Timeline.PaginationDirection): Result<Boolean> = withContext(NonCancellable) {
initLatch.await()
runCatching {
@@ -226,7 +225,6 @@ class RustTimeline(
}.let { items -> loadingIndicatorsPostProcessor.process(items, hasMoreToLoadBackward, hasMoreToLoadForward) }
// Keep lastForwardIndicatorsPostProcessor last
.let { items -> lastForwardIndicatorsPostProcessor.process(items) }
}
override fun close() {

View File

@@ -25,7 +25,6 @@ import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTime
class LastForwardIndicatorsPostProcessor(
private val isTimelineLive: Boolean,
) {
private val lastForwardIdentifiers = LinkedHashSet<String>()
fun process(

View File

@@ -22,7 +22,6 @@ import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTime
import io.element.android.services.toolbox.api.systemclock.SystemClock
class LoadingIndicatorsPostProcessor(private val systemClock: SystemClock) {
fun process(
items: List<MatrixTimelineItem>,
hasMoreToLoadBackward: Boolean,

View File

@@ -29,7 +29,6 @@ import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTime
* or add the RoomBeginning item for non DM room.
*/
class RoomBeginningPostProcessor {
fun process(
items: List<MatrixTimelineItem>,
isDm: Boolean,
@@ -49,7 +48,6 @@ class RoomBeginningPostProcessor {
}
private fun processForDM(items: List<MatrixTimelineItem>): List<MatrixTimelineItem> {
// Find room creation event. This is usually index 0
val roomCreationEventIndex = items.indexOfFirst {
val stateEventContent = (it as? MatrixTimelineItem.Event)?.event?.content as? StateContent
@@ -83,5 +81,4 @@ class RoomBeginningPostProcessor {
virtual = VirtualTimelineItem.RoomBeginning
)
}
}

View File

@@ -32,7 +32,6 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -55,14 +54,37 @@ class FakeTimeline(
),
override val membershipChangeEventReceived: Flow<Unit> = MutableSharedFlow(),
) : Timeline {
var sendMessageLambda: (
body: String,
htmlBody: String?,
mentions: List<Mention>,
) -> Result<Unit> = { _, _, _ ->
Result.success(Unit)
}
var sendMessageLambda: (body: String, htmlBody: String?, mentions: List<Mention>) -> Result<Unit> = { _, _, _ -> Result.success(Unit) }
override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> = sendMessageLambda(body, htmlBody, mentions)
override suspend fun sendMessage(
body: String,
htmlBody: String?,
mentions: List<Mention>,
): Result<Unit> = sendMessageLambda(body, htmlBody, mentions)
var editMessageLambda: (originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String?, mentions: List<Mention>) -> Result<Unit> =
{ _, _, _, _, _ -> Result.success(Unit) }
var editMessageLambda: (
originalEventId: EventId?,
transactionId: TransactionId?,
body: String,
htmlBody: String?,
mentions: List<Mention>,
) -> Result<Unit> = { _, _, _, _, _ ->
Result.success(Unit)
}
override suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> = editMessageLambda(
override suspend fun editMessage(
originalEventId: EventId?,
transactionId: TransactionId?,
body: String,
htmlBody: String?,
mentions: List<Mention>,
): Result<Unit> = editMessageLambda(
originalEventId,
transactionId,
body,
@@ -70,23 +92,52 @@ class FakeTimeline(
mentions
)
var enterSpecialModeLambda: (eventId: EventId?) -> Result<Unit> = { Result.success(Unit) }
var enterSpecialModeLambda: (eventId: EventId?) -> Result<Unit> = {
Result.success(Unit)
}
override suspend fun enterSpecialMode(eventId: EventId?): Result<Unit> = enterSpecialModeLambda(eventId)
var replyMessageLambda: (eventId: EventId, body: String, htmlBody: String?, mentions: List<Mention>) -> Result<Unit> =
{ _, _, _, _ -> Result.success(Unit) }
var replyMessageLambda: (
eventId: EventId,
body: String,
htmlBody: String?,
mentions: List<Mention>,
) -> Result<Unit> = { _, _, _, _ ->
Result.success(Unit)
}
override suspend fun replyMessage(eventId: EventId, body: String, htmlBody: String?, mentions: List<Mention>): Result<Unit> = replyMessageLambda(
override suspend fun replyMessage(
eventId: EventId,
body: String,
htmlBody: String?,
mentions: List<Mention>,
): Result<Unit> = replyMessageLambda(
eventId,
body,
htmlBody,
mentions
)
var sendImageLambda: (file: File, thumbnailFile: File?, imageInfo: ImageInfo, body: String?, formattedBody: String?, progressCallback: ProgressCallback?) -> Result<MediaUploadHandler> =
{ _, _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) }
var sendImageLambda: (
file: File,
thumbnailFile: File?,
imageInfo: ImageInfo,
body: String?,
formattedBody: String?,
progressCallback: ProgressCallback?,
) -> Result<MediaUploadHandler> = { _, _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
override suspend fun sendImage(file: File, thumbnailFile: File?, imageInfo: ImageInfo, body: String?, formattedBody: String?, progressCallback: ProgressCallback?): Result<MediaUploadHandler> = sendImageLambda(
override suspend fun sendImage(
file: File,
thumbnailFile: File?,
imageInfo: ImageInfo,
body: String?,
formattedBody: String?,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> = sendImageLambda(
file,
thumbnailFile,
imageInfo,
@@ -95,10 +146,25 @@ class FakeTimeline(
progressCallback
)
var sendVideoLambda: (file: File, thumbnailFile: File?, videoInfo: VideoInfo, body: String?, formattedBody: String?, progressCallback: ProgressCallback?) -> Result<MediaUploadHandler> =
{ _, _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) }
var sendVideoLambda: (
file: File,
thumbnailFile: File?,
videoInfo: VideoInfo,
body: String?,
formattedBody: String?,
progressCallback: ProgressCallback?,
) -> Result<MediaUploadHandler> = { _, _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
override suspend fun sendVideo(file: File, thumbnailFile: File?, videoInfo: VideoInfo, body: String?, formattedBody: String?, progressCallback: ProgressCallback?): Result<MediaUploadHandler> = sendVideoLambda(
override suspend fun sendVideo(
file: File,
thumbnailFile: File?,
videoInfo: VideoInfo,
body: String?,
formattedBody: String?,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> = sendVideoLambda(
file,
thumbnailFile,
videoInfo,
@@ -107,19 +173,37 @@ class FakeTimeline(
progressCallback
)
var sendAudioLambda: (file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?) -> Result<MediaUploadHandler> =
{ _, _, _ -> Result.success(FakeMediaUploadHandler()) }
var sendAudioLambda: (
file: File,
audioInfo: AudioInfo,
progressCallback: ProgressCallback?,
) -> Result<MediaUploadHandler> = { _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
override suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> = sendAudioLambda(
override suspend fun sendAudio(
file: File,
audioInfo: AudioInfo,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> = sendAudioLambda(
file,
audioInfo,
progressCallback
)
var sendFileLambda: (file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?) -> Result<MediaUploadHandler> =
{ _, _, _ -> Result.success(FakeMediaUploadHandler()) }
var sendFileLambda: (
file: File,
fileInfo: FileInfo,
progressCallback: ProgressCallback?,
) -> Result<MediaUploadHandler> = { _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
override suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> = sendFileLambda(
override suspend fun sendFile(
file: File,
fileInfo: FileInfo,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> = sendFileLambda(
file,
fileInfo,
progressCallback
@@ -137,10 +221,23 @@ class FakeTimeline(
var cancelSendLambda: (transactionId: TransactionId) -> Result<Unit> = { Result.success(Unit) }
override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> = cancelSendLambda(transactionId)
var sendLocationLambda: (body: String, geoUri: String, description: String?, zoomLevel: Int?, assetType: AssetType?) -> Result<Unit> =
{ _, _, _, _, _ -> Result.success(Unit) }
var sendLocationLambda: (
body: String,
geoUri: String,
description: String?,
zoomLevel: Int?,
assetType: AssetType?,
) -> Result<Unit> = { _, _, _, _, _ ->
Result.success(Unit)
}
override suspend fun sendLocation(body: String, geoUri: String, description: String?, zoomLevel: Int?, assetType: AssetType?): Result<Unit> = sendLocationLambda(
override suspend fun sendLocation(
body: String,
geoUri: String,
description: String?,
zoomLevel: Int?,
assetType: AssetType?,
): Result<Unit> = sendLocationLambda(
body,
geoUri,
description,
@@ -148,20 +245,44 @@ class FakeTimeline(
assetType
)
var createPollLambda: (question: String, answers: List<String>, maxSelections: Int, pollKind: PollKind) -> Result<Unit> =
{ _, _, _, _ -> Result.success(Unit) }
var createPollLambda: (
question: String,
answers: List<String>,
maxSelections: Int,
pollKind: PollKind,
) -> Result<Unit> = { _, _, _, _ ->
Result.success(Unit)
}
override suspend fun createPoll(question: String, answers: List<String>, maxSelections: Int, pollKind: PollKind): Result<Unit> = createPollLambda(
override suspend fun createPoll(
question: String,
answers: List<String>,
maxSelections: Int,
pollKind: PollKind,
): Result<Unit> = createPollLambda(
question,
answers,
maxSelections,
pollKind
)
var editPollLambda: (pollStartId: EventId, question: String, answers: List<String>, maxSelections: Int, pollKind: PollKind) -> Result<Unit> =
{ _, _, _, _, _ -> Result.success(Unit) }
var editPollLambda: (
pollStartId: EventId,
question: String,
answers: List<String>,
maxSelections: Int,
pollKind: PollKind,
) -> Result<Unit> = { _, _, _, _, _ ->
Result.success(Unit)
}
override suspend fun editPoll(pollStartId: EventId, question: String, answers: List<String>, maxSelections: Int, pollKind: PollKind): Result<Unit> = editPollLambda(
override suspend fun editPoll(
pollStartId: EventId,
question: String,
answers: List<String>,
maxSelections: Int,
pollKind: PollKind,
): Result<Unit> = editPollLambda(
pollStartId,
question,
answers,
@@ -169,29 +290,67 @@ class FakeTimeline(
pollKind
)
var sendPollResponseLambda: (pollStartId: EventId, answers: List<String>) -> Result<Unit> = { _, _ -> Result.success(Unit) }
override suspend fun sendPollResponse(pollStartId: EventId, answers: List<String>): Result<Unit> = sendPollResponseLambda(pollStartId, answers)
var sendPollResponseLambda: (
pollStartId: EventId,
answers: List<String>,
) -> Result<Unit> = { _, _ ->
Result.success(Unit)
}
var endPollLambda: (pollStartId: EventId, text: String) -> Result<Unit> = { _, _ -> Result.success(Unit) }
override suspend fun endPoll(pollStartId: EventId, text: String): Result<Unit> = endPollLambda(pollStartId, text)
override suspend fun sendPollResponse(
pollStartId: EventId,
answers: List<String>,
): Result<Unit> = sendPollResponseLambda(pollStartId, answers)
var sendVoiceMessageLambda: (file: File, audioInfo: AudioInfo, waveform: List<Float>, progressCallback: ProgressCallback?) -> Result<MediaUploadHandler> =
{ _, _, _, _ -> Result.success(FakeMediaUploadHandler()) }
var endPollLambda: (
pollStartId: EventId,
text: String,
) -> Result<Unit> = { _, _ ->
Result.success(Unit)
}
override suspend fun sendVoiceMessage(file: File, audioInfo: AudioInfo, waveform: List<Float>, progressCallback: ProgressCallback?): Result<MediaUploadHandler> = sendVoiceMessageLambda(
override suspend fun endPoll(
pollStartId: EventId,
text: String,
): Result<Unit> = endPollLambda(pollStartId, text)
var sendVoiceMessageLambda: (
file: File,
audioInfo: AudioInfo,
waveform: List<Float>,
progressCallback: ProgressCallback?,
) -> Result<MediaUploadHandler> = { _, _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
override suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Float>,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> = sendVoiceMessageLambda(
file,
audioInfo,
waveform,
progressCallback
)
var sendReadReceiptLambda: (eventId: EventId, receiptType: ReceiptType) -> Result<Unit> = { _, _ -> Result.success(Unit) }
var sendReadReceiptLambda: (
eventId: EventId,
receiptType: ReceiptType,
) -> Result<Unit> = { _, _ ->
Result.success(Unit)
}
override suspend fun sendReadReceipt(
eventId: EventId,
receiptType: ReceiptType,
): Result<Unit> = sendReadReceiptLambda(eventId, receiptType)
var paginateLambda: (direction: Timeline.PaginationDirection) -> Result<Boolean> = { Result.success(false) }
var paginateLambda: (direction: Timeline.PaginationDirection) -> Result<Boolean> = {
Result.success(false)
}
override suspend fun paginate(direction: Timeline.PaginationDirection): Result<Boolean> = paginateLambda(direction)
override fun paginationStatus(direction: Timeline.PaginationDirection): StateFlow<Timeline.PaginationStatus> {