Start integrating uptodate rust sdk and make it compile (WIP)
This commit is contained in:
parent
a4f9354e8a
commit
0f498a0290
34 changed files with 739 additions and 352 deletions
|
|
@ -20,5 +20,5 @@ import io.element.android.features.messages.actionlist.model.TimelineItemAction
|
|||
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||
|
||||
sealed interface MessagesEvents {
|
||||
data class HandleAction(val action: TimelineItemAction, val messageEvent: TimelineItem.MessageEvent) : MessagesEvents
|
||||
data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : MessagesEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class MessagesPresenter @Inject constructor(
|
|||
}
|
||||
fun handleEvents(event: MessagesEvents) {
|
||||
when (event) {
|
||||
is MessagesEvents.HandleAction -> localCoroutineScope.handleTimelineAction(event.action, event.messageEvent, composerState)
|
||||
is MessagesEvents.HandleAction -> localCoroutineScope.handleTimelineAction(event.action, event.event, composerState)
|
||||
}
|
||||
}
|
||||
return MessagesState(
|
||||
|
|
@ -94,7 +94,7 @@ class MessagesPresenter @Inject constructor(
|
|||
|
||||
fun CoroutineScope.handleTimelineAction(
|
||||
action: TimelineItemAction,
|
||||
targetEvent: TimelineItem.MessageEvent,
|
||||
targetEvent: TimelineItem.Event,
|
||||
composerState: MessageComposerState,
|
||||
) = launch {
|
||||
when (action) {
|
||||
|
|
@ -110,11 +110,11 @@ class MessagesPresenter @Inject constructor(
|
|||
Timber.v("NotImplementedYet")
|
||||
}
|
||||
|
||||
private suspend fun handleActionRedact(event: TimelineItem.MessageEvent) {
|
||||
private suspend fun handleActionRedact(event: TimelineItem.Event) {
|
||||
room.redactEvent(event.id)
|
||||
}
|
||||
|
||||
private fun handleActionEdit(targetEvent: TimelineItem.MessageEvent, composerState: MessageComposerState) {
|
||||
private fun handleActionEdit(targetEvent: TimelineItem.Event, composerState: MessageComposerState) {
|
||||
val composerMode = MessageComposerMode.Edit(
|
||||
targetEvent.id,
|
||||
(targetEvent.content as? TimelineItemTextBasedContent)?.body.orEmpty()
|
||||
|
|
@ -124,7 +124,7 @@ class MessagesPresenter @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun handleActionReply(targetEvent: TimelineItem.MessageEvent, composerState: MessageComposerState) {
|
||||
private fun handleActionReply(targetEvent: TimelineItem.Event, composerState: MessageComposerState) {
|
||||
val composerMode = MessageComposerMode.Reply(targetEvent.safeSenderName, targetEvent.id, "")
|
||||
composerState.eventSink(
|
||||
MessageComposerEvents.SetMode(composerMode)
|
||||
|
|
|
|||
|
|
@ -84,21 +84,21 @@ fun MessagesView(
|
|||
|
||||
LogCompositions(tag = "MessagesScreen", msg = "Content")
|
||||
|
||||
fun onMessageClicked(messageEvent: TimelineItem.MessageEvent) {
|
||||
Timber.v("OnMessageClicked= ${messageEvent.id}")
|
||||
fun onMessageClicked(event: TimelineItem.Event) {
|
||||
Timber.v("OnMessageClicked= ${event.id}")
|
||||
}
|
||||
|
||||
fun onMessageLongClicked(messageEvent: TimelineItem.MessageEvent) {
|
||||
Timber.v("OnMessageLongClicked= ${messageEvent.id}")
|
||||
fun onMessageLongClicked(event: TimelineItem.Event) {
|
||||
Timber.v("OnMessageLongClicked= ${event.id}")
|
||||
focusManager.clearFocus(force = true)
|
||||
state.actionListState.eventSink(ActionListEvents.ComputeForMessage(messageEvent))
|
||||
state.actionListState.eventSink(ActionListEvents.ComputeForMessage(event))
|
||||
coroutineScope.launch {
|
||||
itemActionsBottomSheetState.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun onActionSelected(action: TimelineItemAction, messageEvent: TimelineItem.MessageEvent) {
|
||||
state.eventSink(MessagesEvents.HandleAction(action, messageEvent))
|
||||
fun onActionSelected(action: TimelineItemAction, event: TimelineItem.Event) {
|
||||
state.eventSink(MessagesEvents.HandleAction(action, event))
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
|
|
@ -138,8 +138,8 @@ fun MessagesView(
|
|||
fun MessagesViewContent(
|
||||
state: MessagesState,
|
||||
modifier: Modifier = Modifier,
|
||||
onMessageClicked: (TimelineItem.MessageEvent) -> Unit = {},
|
||||
onMessageLongClicked: (TimelineItem.MessageEvent) -> Unit = {},
|
||||
onMessageClicked: (TimelineItem.Event) -> Unit = {},
|
||||
onMessageLongClicked: (TimelineItem.Event) -> Unit = {},
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
|
|
|
|||
|
|
@ -20,5 +20,5 @@ import io.element.android.features.messages.timeline.model.TimelineItem
|
|||
|
||||
sealed interface ActionListEvents {
|
||||
object Clear : ActionListEvents
|
||||
data class ComputeForMessage(val messageEvent: TimelineItem.MessageEvent) : ActionListEvents
|
||||
data class ComputeForMessage(val event: TimelineItem.Event) : ActionListEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class ActionListPresenter @Inject constructor() : Presenter<ActionListState> {
|
|||
fun handleEvents(event: ActionListEvents) {
|
||||
when (event) {
|
||||
ActionListEvents.Clear -> target.value = ActionListState.Target.None
|
||||
is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage(event.messageEvent, target)
|
||||
is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage(event.event, target)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ class ActionListPresenter @Inject constructor() : Presenter<ActionListState> {
|
|||
)
|
||||
}
|
||||
|
||||
fun CoroutineScope.computeForMessage(timelineItem: TimelineItem.MessageEvent, target: MutableState<ActionListState.Target>) = launch {
|
||||
fun CoroutineScope.computeForMessage(timelineItem: TimelineItem.Event, target: MutableState<ActionListState.Target>) = launch {
|
||||
target.value = ActionListState.Target.Loading(timelineItem)
|
||||
val actions =
|
||||
if (timelineItem.content is TimelineItemRedactedContent) {
|
||||
|
|
|
|||
|
|
@ -29,9 +29,9 @@ data class ActionListState(
|
|||
|
||||
sealed interface Target {
|
||||
object None : Target
|
||||
data class Loading(val messageEvent: TimelineItem.MessageEvent) : Target
|
||||
data class Loading(val event: TimelineItem.Event) : Target
|
||||
data class Success(
|
||||
val messageEvent: TimelineItem.MessageEvent,
|
||||
val event: TimelineItem.Event,
|
||||
val actions: ImmutableList<TimelineItemAction>,
|
||||
) : Target
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ import kotlinx.coroutines.launch
|
|||
fun ActionListView(
|
||||
state: ActionListState,
|
||||
modalBottomSheetState: ModalBottomSheetState,
|
||||
onActionSelected: (action: TimelineItemAction, TimelineItem.MessageEvent) -> Unit,
|
||||
onActionSelected: (action: TimelineItemAction, TimelineItem.Event) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
|
@ -65,7 +65,7 @@ fun ActionListView(
|
|||
|
||||
fun onItemActionClicked(
|
||||
itemAction: TimelineItemAction,
|
||||
targetItem: TimelineItem.MessageEvent
|
||||
targetItem: TimelineItem.Event
|
||||
) {
|
||||
onActionSelected(itemAction, targetItem)
|
||||
coroutineScope.launch {
|
||||
|
|
@ -92,7 +92,7 @@ fun ActionListView(
|
|||
private fun SheetContent(
|
||||
state: ActionListState,
|
||||
modifier: Modifier = Modifier,
|
||||
onActionClicked: (TimelineItemAction, TimelineItem.MessageEvent) -> Unit = { _, _ -> },
|
||||
onActionClicked: (TimelineItemAction, TimelineItem.Event) -> Unit = { _, _ -> },
|
||||
) {
|
||||
when (val target = state.target) {
|
||||
is ActionListState.Target.Loading,
|
||||
|
|
@ -110,7 +110,7 @@ private fun SheetContent(
|
|||
) { action ->
|
||||
ListItem(
|
||||
modifier = Modifier.clickable {
|
||||
onActionClicked(action, target.messageEvent)
|
||||
onActionClicked(action, target.event)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -1,252 +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.features.messages.timeline
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import io.element.android.features.messages.timeline.diff.CacheInvalidator
|
||||
import io.element.android.features.messages.timeline.diff.MatrixTimelineItemsDiffCallback
|
||||
import io.element.android.features.messages.timeline.model.AggregatedReaction
|
||||
import io.element.android.features.messages.timeline.model.MessagesItemGroupPosition
|
||||
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.timeline.model.TimelineItemReactions
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemEmoteContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemEncryptedContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemImageContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemNoticeContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemRedactedContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemTextContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemUnknownContent
|
||||
import io.element.android.features.messages.timeline.util.invalidateLast
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.core.EventId
|
||||
import io.element.android.libraries.matrix.media.MediaResolver
|
||||
import io.element.android.libraries.matrix.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.ui.MatrixItemHelper
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.matrix.rustcomponents.sdk.FormattedBody
|
||||
import org.matrix.rustcomponents.sdk.MessageFormat
|
||||
import org.matrix.rustcomponents.sdk.MessageType
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
class TimelineItemsFactory @Inject constructor(
|
||||
private val matrixItemHelper: MatrixItemHelper,
|
||||
private val room: MatrixRoom,
|
||||
private val dispatcher: CoroutineDispatcher,
|
||||
) {
|
||||
|
||||
private val timelineItems = MutableStateFlow<List<TimelineItem>>(emptyList())
|
||||
private val timelineItemsCache = arrayListOf<TimelineItem?>()
|
||||
|
||||
// Items from rust sdk, used for diffing
|
||||
private var matrixTimelineItems: List<MatrixTimelineItem> = emptyList()
|
||||
|
||||
private val lock = Mutex()
|
||||
private val cacheInvalidator = CacheInvalidator(timelineItemsCache)
|
||||
|
||||
fun flow(): StateFlow<List<TimelineItem>> = timelineItems.asStateFlow()
|
||||
|
||||
suspend fun replaceWith(
|
||||
timelineItems: List<MatrixTimelineItem>,
|
||||
) = withContext(dispatcher) {
|
||||
lock.withLock {
|
||||
calculateAndApplyDiff(timelineItems)
|
||||
buildAndEmitTimelineItemStates(timelineItems)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun pushItem(
|
||||
timelineItem: MatrixTimelineItem,
|
||||
) = withContext(dispatcher) {
|
||||
lock.withLock {
|
||||
// Makes sure to invalidate last as we need to recompute some data (like groupPosition)
|
||||
timelineItemsCache.invalidateLast()
|
||||
timelineItemsCache.add(null)
|
||||
matrixTimelineItems = matrixTimelineItems + timelineItem
|
||||
buildAndEmitTimelineItemStates(matrixTimelineItems)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun buildAndEmitTimelineItemStates(timelineItems: List<MatrixTimelineItem>) {
|
||||
val newTimelineItemStates = ArrayList<TimelineItem>()
|
||||
for (index in timelineItemsCache.indices.reversed()) {
|
||||
val cacheItem = timelineItemsCache[index]
|
||||
if (cacheItem == null) {
|
||||
buildAndCacheItem(timelineItems, index)?.also { timelineItemState ->
|
||||
newTimelineItemStates.add(timelineItemState)
|
||||
}
|
||||
} else {
|
||||
newTimelineItemStates.add(cacheItem)
|
||||
}
|
||||
}
|
||||
this.timelineItems.emit(newTimelineItemStates)
|
||||
}
|
||||
|
||||
private fun calculateAndApplyDiff(newTimelineItems: List<MatrixTimelineItem>) {
|
||||
val timeToDiff = measureTimeMillis {
|
||||
val diffCallback =
|
||||
MatrixTimelineItemsDiffCallback(
|
||||
oldList = matrixTimelineItems,
|
||||
newList = newTimelineItems
|
||||
)
|
||||
val diffResult = DiffUtil.calculateDiff(diffCallback, false)
|
||||
matrixTimelineItems = newTimelineItems
|
||||
diffResult.dispatchUpdatesTo(cacheInvalidator)
|
||||
}
|
||||
Timber.v("Time to apply diff on new list of ${newTimelineItems.size} items: $timeToDiff ms")
|
||||
}
|
||||
|
||||
private suspend fun buildAndCacheItem(
|
||||
timelineItems: List<MatrixTimelineItem>,
|
||||
index: Int
|
||||
): TimelineItem? {
|
||||
val timelineItemState =
|
||||
when (val currentTimelineItem = timelineItems[index]) {
|
||||
is MatrixTimelineItem.Event -> {
|
||||
buildMessageEvent(
|
||||
currentTimelineItem,
|
||||
index,
|
||||
timelineItems,
|
||||
)
|
||||
}
|
||||
is MatrixTimelineItem.Virtual -> TimelineItem.Virtual(
|
||||
"virtual_item_$index"
|
||||
)
|
||||
MatrixTimelineItem.Other -> null
|
||||
}
|
||||
timelineItemsCache[index] = timelineItemState
|
||||
return timelineItemState
|
||||
}
|
||||
|
||||
private suspend fun buildMessageEvent(
|
||||
currentTimelineItem: MatrixTimelineItem.Event,
|
||||
index: Int,
|
||||
timelineItems: List<MatrixTimelineItem>,
|
||||
): TimelineItem.MessageEvent {
|
||||
val currentSender = currentTimelineItem.event.sender()
|
||||
val groupPosition =
|
||||
computeGroupPosition(currentTimelineItem, timelineItems, index)
|
||||
val senderDisplayName = room.userDisplayName(currentSender).getOrNull()
|
||||
val senderAvatarUrl = room.userAvatarUrl(currentSender).getOrNull()
|
||||
val senderAvatarData = AvatarData(
|
||||
name = senderDisplayName ?: currentSender,
|
||||
url = senderAvatarUrl,
|
||||
size = AvatarSize.SMALL
|
||||
)
|
||||
return TimelineItem.MessageEvent(
|
||||
id = EventId(currentTimelineItem.uniqueId),
|
||||
senderId = currentSender,
|
||||
senderDisplayName = senderDisplayName,
|
||||
senderAvatar = senderAvatarData,
|
||||
content = currentTimelineItem.computeContent(),
|
||||
isMine = currentTimelineItem.event.isOwn(),
|
||||
groupPosition = groupPosition,
|
||||
reactionsState = currentTimelineItem.computeReactionsState()
|
||||
)
|
||||
}
|
||||
|
||||
private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions {
|
||||
val aggregatedReactions = event.reactions().map {
|
||||
AggregatedReaction(key = it.key, count = it.count.toString(), isHighlighted = false)
|
||||
}
|
||||
return TimelineItemReactions(aggregatedReactions.toImmutableList())
|
||||
}
|
||||
|
||||
private fun MatrixTimelineItem.Event.computeContent(): TimelineItemContent {
|
||||
val content = event.content()
|
||||
content.asUnableToDecrypt()?.let { encryptedMessage ->
|
||||
return TimelineItemEncryptedContent(encryptedMessage)
|
||||
}
|
||||
if (content.isRedactedMessage()) {
|
||||
return TimelineItemRedactedContent
|
||||
}
|
||||
val contentAsMessage = content.asMessage()
|
||||
return when (val messageType = contentAsMessage?.msgtype()) {
|
||||
is MessageType.Emote -> TimelineItemEmoteContent(
|
||||
body = messageType.content.body,
|
||||
htmlDocument = messageType.content.formatted?.toHtmlDocument()
|
||||
)
|
||||
is MessageType.Image -> {
|
||||
val height = messageType.content.info?.height?.toFloat()
|
||||
val width = messageType.content.info?.width?.toFloat()
|
||||
val aspectRatio = if (height != null && width != null) {
|
||||
width / height
|
||||
} else {
|
||||
0.7f
|
||||
}
|
||||
TimelineItemImageContent(
|
||||
body = messageType.content.body,
|
||||
imageMeta = MediaResolver.Meta(
|
||||
source = messageType.content.source,
|
||||
kind = MediaResolver.Kind.Content
|
||||
),
|
||||
blurhash = messageType.content.info?.blurhash,
|
||||
aspectRatio = aspectRatio
|
||||
)
|
||||
}
|
||||
is MessageType.Notice -> TimelineItemNoticeContent(
|
||||
body = messageType.content.body,
|
||||
htmlDocument = messageType.content.formatted?.toHtmlDocument()
|
||||
)
|
||||
is MessageType.Text -> TimelineItemTextContent(
|
||||
body = messageType.content.body,
|
||||
htmlDocument = messageType.content.formatted?.toHtmlDocument()
|
||||
)
|
||||
else -> TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
||||
private fun FormattedBody.toHtmlDocument(): Document? {
|
||||
return takeIf { it.format == MessageFormat.HTML }?.body?.let { formattedBody ->
|
||||
Jsoup.parse(formattedBody)
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeGroupPosition(
|
||||
currentTimelineItem: MatrixTimelineItem.Event,
|
||||
timelineItems: List<MatrixTimelineItem>,
|
||||
index: Int
|
||||
): MessagesItemGroupPosition {
|
||||
val prevTimelineItem =
|
||||
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()
|
||||
|
||||
return when {
|
||||
previousSender != currentSender && nextSender == currentSender -> MessagesItemGroupPosition.First
|
||||
previousSender == currentSender && nextSender == currentSender -> MessagesItemGroupPosition.Middle
|
||||
previousSender == currentSender && nextSender != currentSender -> MessagesItemGroupPosition.Last
|
||||
else -> MessagesItemGroupPosition.None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,14 +24,12 @@ import androidx.compose.runtime.collectAsState
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import io.element.android.features.messages.timeline.factories.TimelineItemsFactory
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.MatrixClient
|
||||
import io.element.android.libraries.matrix.core.EventId
|
||||
import io.element.android.libraries.matrix.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.timeline.MatrixTimeline
|
||||
import io.element.android.libraries.matrix.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.ui.MatrixItemHelper
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
|
@ -39,18 +37,15 @@ import kotlinx.coroutines.flow.onEach
|
|||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val PAGINATION_COUNT = 50
|
||||
private const val backPaginationEventLimit = 20
|
||||
private const val backPaginationPageSize = 50
|
||||
|
||||
class TimelinePresenter @Inject constructor(
|
||||
coroutineDispatchers: CoroutineDispatchers,
|
||||
client: MatrixClient,
|
||||
private val timelineItemsFactory: TimelineItemsFactory,
|
||||
room: MatrixRoom,
|
||||
) : Presenter<TimelineState> {
|
||||
|
||||
private val timeline = room.timeline()
|
||||
private val matrixItemHelper = MatrixItemHelper(client)
|
||||
private val timelineItemsFactory =
|
||||
TimelineItemsFactory(matrixItemHelper, room, coroutineDispatchers.computation)
|
||||
|
||||
private class TimelineCallback(
|
||||
private val coroutineScope: CoroutineScope,
|
||||
|
|
@ -66,9 +61,6 @@ class TimelinePresenter @Inject constructor(
|
|||
@Composable
|
||||
override fun present(): TimelineState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val hasMoreToLoad = rememberSaveable {
|
||||
mutableStateOf(timeline.hasMoreToLoad)
|
||||
}
|
||||
val highlightedEventId: MutableState<EventId?> = rememberSaveable {
|
||||
mutableStateOf(null)
|
||||
}
|
||||
|
|
@ -78,7 +70,7 @@ class TimelinePresenter @Inject constructor(
|
|||
|
||||
fun handleEvents(event: TimelineEvents) {
|
||||
when (event) {
|
||||
TimelineEvents.LoadMore -> localCoroutineScope.loadMore(hasMoreToLoad)
|
||||
TimelineEvents.LoadMore -> localCoroutineScope.loadMore()
|
||||
is TimelineEvents.SetHighlightedEvent -> highlightedEventId.value = event.eventId
|
||||
}
|
||||
}
|
||||
|
|
@ -102,13 +94,11 @@ class TimelinePresenter @Inject constructor(
|
|||
return TimelineState(
|
||||
highlightedEventId = highlightedEventId.value,
|
||||
timelineItems = timelineItems.value.toImmutableList(),
|
||||
hasMoreToLoad = hasMoreToLoad.value,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.loadMore(hasMoreToLoad: MutableState<Boolean>) = launch {
|
||||
timeline.paginateBackwards(PAGINATION_COUNT)
|
||||
hasMoreToLoad.value = timeline.hasMoreToLoad
|
||||
private fun CoroutineScope.loadMore() = launch {
|
||||
timeline.paginateBackwards(backPaginationEventLimit, backPaginationPageSize)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
@Immutable
|
||||
data class TimelineState(
|
||||
val timelineItems: ImmutableList<TimelineItem>,
|
||||
val hasMoreToLoad: Boolean,
|
||||
val highlightedEventId: EventId?,
|
||||
val eventSink: (TimelineEvents) -> Unit
|
||||
)
|
||||
|
|
|
|||
|
|
@ -90,8 +90,8 @@ import kotlinx.coroutines.launch
|
|||
fun TimelineView(
|
||||
state: TimelineState,
|
||||
modifier: Modifier = Modifier,
|
||||
onMessageClicked: (TimelineItem.MessageEvent) -> Unit = {},
|
||||
onMessageLongClicked: (TimelineItem.MessageEvent) -> Unit = {},
|
||||
onMessageClicked: (TimelineItem.Event) -> Unit = {},
|
||||
onMessageLongClicked: (TimelineItem.Event) -> Unit = {},
|
||||
) {
|
||||
val lazyListState = rememberLazyListState()
|
||||
Box(modifier = modifier) {
|
||||
|
|
@ -114,11 +114,14 @@ fun TimelineView(
|
|||
onLongClick = onMessageLongClicked
|
||||
)
|
||||
}
|
||||
/*
|
||||
if (state.hasMoreToLoad) {
|
||||
item {
|
||||
TimelineLoadingMoreIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
fun onReachedLoadMore() {
|
||||
|
|
@ -135,14 +138,14 @@ fun TimelineView(
|
|||
|
||||
private fun TimelineItem.key(): String {
|
||||
return when (this) {
|
||||
is TimelineItem.MessageEvent -> id.value
|
||||
is TimelineItem.Event -> id.value
|
||||
is TimelineItem.Virtual -> id
|
||||
}
|
||||
}
|
||||
|
||||
private fun TimelineItem.contentType(): Int {
|
||||
return when (this) {
|
||||
is TimelineItem.MessageEvent -> 0
|
||||
is TimelineItem.Event -> 0
|
||||
is TimelineItem.Virtual -> 1
|
||||
}
|
||||
}
|
||||
|
|
@ -151,13 +154,13 @@ private fun TimelineItem.contentType(): Int {
|
|||
fun TimelineItemRow(
|
||||
timelineItem: TimelineItem,
|
||||
isHighlighted: Boolean,
|
||||
onClick: (TimelineItem.MessageEvent) -> Unit,
|
||||
onLongClick: (TimelineItem.MessageEvent) -> Unit,
|
||||
onClick: (TimelineItem.Event) -> Unit,
|
||||
onLongClick: (TimelineItem.Event) -> Unit,
|
||||
) {
|
||||
when (timelineItem) {
|
||||
is TimelineItem.Virtual -> return
|
||||
is TimelineItem.MessageEvent -> MessageEventRow(
|
||||
messageEvent = timelineItem,
|
||||
is TimelineItem.Event -> MessageEventRow(
|
||||
event = timelineItem,
|
||||
isHighlighted = isHighlighted,
|
||||
onClick = { onClick(timelineItem) },
|
||||
onLongClick = { onLongClick(timelineItem) }
|
||||
|
|
@ -167,14 +170,14 @@ fun TimelineItemRow(
|
|||
|
||||
@Composable
|
||||
fun MessageEventRow(
|
||||
messageEvent: TimelineItem.MessageEvent,
|
||||
event: TimelineItem.Event,
|
||||
isHighlighted: Boolean,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val (parentAlignment, contentAlignment) = if (messageEvent.isMine) {
|
||||
val (parentAlignment, contentAlignment) = if (event.isMine) {
|
||||
Pair(Alignment.CenterEnd, Alignment.End)
|
||||
} else {
|
||||
Pair(Alignment.CenterStart, Alignment.Start)
|
||||
|
|
@ -186,20 +189,20 @@ fun MessageEventRow(
|
|||
contentAlignment = parentAlignment
|
||||
) {
|
||||
Row {
|
||||
if (!messageEvent.isMine) {
|
||||
if (!event.isMine) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
}
|
||||
Column(horizontalAlignment = contentAlignment) {
|
||||
if (messageEvent.showSenderInformation) {
|
||||
if (event.showSenderInformation) {
|
||||
MessageSenderInformation(
|
||||
messageEvent.safeSenderName,
|
||||
messageEvent.senderAvatar,
|
||||
event.safeSenderName,
|
||||
event.senderAvatar,
|
||||
Modifier.zIndex(1f)
|
||||
)
|
||||
}
|
||||
MessageEventBubble(
|
||||
groupPosition = messageEvent.groupPosition,
|
||||
isMine = messageEvent.isMine,
|
||||
groupPosition = event.groupPosition,
|
||||
isMine = event.isMine,
|
||||
interactionSource = interactionSource,
|
||||
isHighlighted = isHighlighted,
|
||||
onClick = onClick,
|
||||
|
|
@ -209,45 +212,45 @@ fun MessageEventRow(
|
|||
.widthIn(max = 320.dp)
|
||||
) {
|
||||
val contentModifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp)
|
||||
when (messageEvent.content) {
|
||||
when (event.content) {
|
||||
is TimelineItemEncryptedContent -> TimelineItemEncryptedView(
|
||||
content = messageEvent.content,
|
||||
content = event.content,
|
||||
modifier = contentModifier
|
||||
)
|
||||
is TimelineItemRedactedContent -> TimelineItemRedactedView(
|
||||
content = messageEvent.content,
|
||||
content = event.content,
|
||||
modifier = contentModifier
|
||||
)
|
||||
is TimelineItemTextBasedContent -> TimelineItemTextView(
|
||||
content = messageEvent.content,
|
||||
content = event.content,
|
||||
interactionSource = interactionSource,
|
||||
modifier = contentModifier,
|
||||
onTextClicked = onClick,
|
||||
onTextLongClicked = onLongClick
|
||||
)
|
||||
is TimelineItemUnknownContent -> TimelineItemUnknownView(
|
||||
content = messageEvent.content,
|
||||
content = event.content,
|
||||
modifier = contentModifier
|
||||
)
|
||||
is TimelineItemImageContent -> TimelineItemImageView(
|
||||
content = messageEvent.content,
|
||||
content = event.content,
|
||||
modifier = contentModifier
|
||||
)
|
||||
}
|
||||
}
|
||||
TimelineItemReactionsView(
|
||||
reactionsState = messageEvent.reactionsState,
|
||||
reactionsState = event.reactionsState,
|
||||
modifier = Modifier
|
||||
.zIndex(1f)
|
||||
.offset(x = if (messageEvent.isMine) 0.dp else 20.dp, y = -(16.dp))
|
||||
.offset(x = if (event.isMine) 0.dp else 20.dp, y = -(16.dp))
|
||||
)
|
||||
}
|
||||
if (messageEvent.isMine) {
|
||||
if (event.isMine) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (messageEvent.groupPosition.isNew()) {
|
||||
if (event.groupPosition.isNew()) {
|
||||
Spacer(modifier = modifier.height(8.dp))
|
||||
} else {
|
||||
Spacer(modifier = modifier.height(2.dp))
|
||||
|
|
@ -399,7 +402,6 @@ fun TimelineItemsPreview(
|
|||
TimelineView(
|
||||
state = TimelineState(
|
||||
timelineItems = timelineItems,
|
||||
hasMoreToLoad = true,
|
||||
highlightedEventId = null,
|
||||
eventSink = {}
|
||||
)
|
||||
|
|
@ -411,7 +413,7 @@ private fun createMessageEvent(
|
|||
content: TimelineItemContent,
|
||||
groupPosition: MessagesItemGroupPosition
|
||||
): TimelineItem {
|
||||
return TimelineItem.MessageEvent(
|
||||
return TimelineItem.Event(
|
||||
id = EventId(Math.random().toString()),
|
||||
senderId = "senderId",
|
||||
senderAvatar = AvatarData("sender"),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.features.messages.timeline.factories
|
||||
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import javax.inject.Inject
|
||||
|
||||
typealias RustTimelineItemContent = org.matrix.rustcomponents.sdk.TimelineItemContent
|
||||
|
||||
class TimelineItemContentFactory @Inject constructor(
|
||||
private val messageFactory: TimelineItemContentMessageFactory,
|
||||
private val redactedMessageFactory: TimelineItemContentRedactedFactory,
|
||||
private val stickerFactory: TimelineItemContentStickerFactory,
|
||||
private val utdFactory: TimelineItemContentUTDFactory,
|
||||
private val roomMembershipFactory: TimelineItemContentRoomMembershipFactory,
|
||||
private val profileChangeFactory: TimelineItemContentProfileChangeFactory,
|
||||
private val stateFactory: TimelineItemContentStateFactory,
|
||||
private val failedToParseMessageFactory: TimelineItemContentFailedToParseMessageFactory,
|
||||
private val failedToParseStateFactory: TimelineItemContentFailedToParseStateFactory
|
||||
) {
|
||||
|
||||
fun create(itemContent: RustTimelineItemContent): TimelineItemContent {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.features.messages.timeline.factories
|
||||
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemUnknownContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentFailedToParseMessageFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.FailedToParseMessageLike): TimelineItemContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
|
@ -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.features.messages.timeline.factories
|
||||
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemUnknownContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentFailedToParseStateFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.FailedToParseState): TimelineItemContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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.features.messages.timeline.factories
|
||||
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemEmoteContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemImageContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemNoticeContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemTextContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemUnknownContent
|
||||
import io.element.android.features.messages.timeline.util.toHtmlDocument
|
||||
import io.element.android.libraries.matrix.media.MediaResolver
|
||||
import org.matrix.rustcomponents.sdk.Message
|
||||
import org.matrix.rustcomponents.sdk.MessageType
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentMessageFactory @Inject constructor() {
|
||||
|
||||
fun create(contentAsMessage: Message?): TimelineItemContent {
|
||||
return when (val messageType = contentAsMessage?.msgtype()) {
|
||||
is MessageType.Emote -> TimelineItemEmoteContent(
|
||||
body = messageType.content.body,
|
||||
htmlDocument = messageType.content.formatted?.toHtmlDocument()
|
||||
)
|
||||
is MessageType.Image -> {
|
||||
val height = messageType.content.info?.height?.toFloat()
|
||||
val width = messageType.content.info?.width?.toFloat()
|
||||
val aspectRatio = if (height != null && width != null) {
|
||||
width / height
|
||||
} else {
|
||||
0.7f
|
||||
}
|
||||
TimelineItemImageContent(
|
||||
body = messageType.content.body,
|
||||
imageMeta = MediaResolver.Meta(
|
||||
source = messageType.content.source,
|
||||
kind = MediaResolver.Kind.Content
|
||||
),
|
||||
blurhash = messageType.content.info?.blurhash,
|
||||
aspectRatio = aspectRatio
|
||||
)
|
||||
}
|
||||
is MessageType.Notice -> TimelineItemNoticeContent(
|
||||
body = messageType.content.body,
|
||||
htmlDocument = messageType.content.formatted?.toHtmlDocument()
|
||||
)
|
||||
is MessageType.Text -> TimelineItemTextContent(
|
||||
body = messageType.content.body,
|
||||
htmlDocument = messageType.content.formatted?.toHtmlDocument()
|
||||
)
|
||||
else -> TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.features.messages.timeline.factories
|
||||
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemRedactedContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemUnknownContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentProfileChangeFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.ProfileChange): TimelineItemContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
|
@ -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.features.messages.timeline.factories
|
||||
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemRedactedContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentRedactedFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.RedactedMessage): TimelineItemContent {
|
||||
return TimelineItemRedactedContent
|
||||
}
|
||||
}
|
||||
|
|
@ -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.features.messages.timeline.factories
|
||||
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemUnknownContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentRoomMembershipFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.RoomMembership): TimelineItemContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
|
@ -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.features.messages.timeline.factories
|
||||
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemUnknownContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentStateFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.State): TimelineItemContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
|
@ -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.features.messages.timeline.factories
|
||||
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemUnknownContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentStickerFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.Sticker): TimelineItemContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
|
@ -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.features.messages.timeline.factories
|
||||
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemContent
|
||||
import io.element.android.features.messages.timeline.model.content.TimelineItemEncryptedContent
|
||||
import org.matrix.rustcomponents.sdk.TimelineItemContentKind
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemContentUTDFactory @Inject constructor() {
|
||||
|
||||
fun create(kind: TimelineItemContentKind.UnableToDecrypt): TimelineItemContent {
|
||||
return TimelineItemEncryptedContent(kind.msg)
|
||||
}
|
||||
}
|
||||
|
|
@ -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.features.messages.timeline.factories
|
||||
|
||||
import io.element.android.features.messages.timeline.model.AggregatedReaction
|
||||
import io.element.android.features.messages.timeline.model.MessagesItemGroupPosition
|
||||
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.timeline.model.TimelineItemReactions
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.core.EventId
|
||||
import io.element.android.libraries.matrix.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.timeline.MatrixTimelineItem
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemEventFactory @Inject constructor(
|
||||
private val room: MatrixRoom,
|
||||
private val contentFactory: TimelineItemContentFactory,
|
||||
) {
|
||||
|
||||
suspend fun create(
|
||||
currentTimelineItem: MatrixTimelineItem.Event,
|
||||
index: Int,
|
||||
timelineItems: List<MatrixTimelineItem>,
|
||||
): TimelineItem.Event {
|
||||
val currentSender = currentTimelineItem.event.sender()
|
||||
val groupPosition =
|
||||
computeGroupPosition(currentTimelineItem, timelineItems, index)
|
||||
val senderDisplayName = room.userDisplayName(currentSender).getOrNull()
|
||||
val senderAvatarUrl = room.userAvatarUrl(currentSender).getOrNull()
|
||||
val senderAvatarData = AvatarData(
|
||||
name = senderDisplayName ?: currentSender,
|
||||
url = senderAvatarUrl,
|
||||
size = AvatarSize.SMALL
|
||||
)
|
||||
return TimelineItem.Event(
|
||||
id = EventId(currentTimelineItem.uniqueId),
|
||||
senderId = currentSender,
|
||||
senderDisplayName = senderDisplayName,
|
||||
senderAvatar = senderAvatarData,
|
||||
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 {
|
||||
AggregatedReaction(key = it.key, count = it.count.toString(), isHighlighted = false)
|
||||
}
|
||||
return TimelineItemReactions(aggregatedReactions.toImmutableList())
|
||||
}
|
||||
|
||||
private fun computeGroupPosition(
|
||||
currentTimelineItem: MatrixTimelineItem.Event,
|
||||
timelineItems: List<MatrixTimelineItem>,
|
||||
index: Int
|
||||
): MessagesItemGroupPosition {
|
||||
val prevTimelineItem =
|
||||
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()
|
||||
|
||||
return when {
|
||||
previousSender != currentSender && nextSender == currentSender -> MessagesItemGroupPosition.First
|
||||
previousSender == currentSender && nextSender == currentSender -> MessagesItemGroupPosition.Middle
|
||||
previousSender == currentSender && nextSender != currentSender -> MessagesItemGroupPosition.Last
|
||||
else -> MessagesItemGroupPosition.None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.features.messages.timeline.factories
|
||||
|
||||
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||
import io.element.android.libraries.matrix.timeline.MatrixTimelineItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineItemVirtualFactory @Inject constructor() {
|
||||
|
||||
suspend fun create(
|
||||
currentTimelineItem: MatrixTimelineItem.Virtual,
|
||||
index: Int,
|
||||
timelineItems: List<MatrixTimelineItem>,
|
||||
): TimelineItem.Virtual {
|
||||
return TimelineItem.Virtual(
|
||||
id = "virtual_item_$index"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* 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.features.messages.timeline.factories
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import io.element.android.features.messages.timeline.diff.CacheInvalidator
|
||||
import io.element.android.features.messages.timeline.diff.MatrixTimelineItemsDiffCallback
|
||||
import io.element.android.features.messages.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.timeline.util.invalidateLast
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.timeline.MatrixTimelineItem
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
class TimelineItemsFactory @Inject constructor(
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val eventItemFactory: TimelineItemEventFactory,
|
||||
private val virtualItemFactory: TimelineItemVirtualFactory,
|
||||
) {
|
||||
|
||||
private val timelineItems = MutableStateFlow<List<TimelineItem>>(emptyList())
|
||||
private val timelineItemsCache = arrayListOf<TimelineItem?>()
|
||||
|
||||
// Items from rust sdk, used for diffing
|
||||
private var matrixTimelineItems: List<MatrixTimelineItem> = emptyList()
|
||||
|
||||
private val lock = Mutex()
|
||||
private val cacheInvalidator = CacheInvalidator(timelineItemsCache)
|
||||
|
||||
fun flow(): StateFlow<List<TimelineItem>> = timelineItems.asStateFlow()
|
||||
|
||||
suspend fun replaceWith(
|
||||
timelineItems: List<MatrixTimelineItem>,
|
||||
) = withContext(dispatchers.computation) {
|
||||
lock.withLock {
|
||||
calculateAndApplyDiff(timelineItems)
|
||||
buildAndEmitTimelineItemStates(timelineItems)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun pushItem(
|
||||
timelineItem: MatrixTimelineItem,
|
||||
) = withContext(dispatchers.computation) {
|
||||
lock.withLock {
|
||||
// Makes sure to invalidate last as we need to recompute some data (like groupPosition)
|
||||
timelineItemsCache.invalidateLast()
|
||||
timelineItemsCache.add(null)
|
||||
matrixTimelineItems = matrixTimelineItems + timelineItem
|
||||
buildAndEmitTimelineItemStates(matrixTimelineItems)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun buildAndEmitTimelineItemStates(timelineItems: List<MatrixTimelineItem>) {
|
||||
val newTimelineItemStates = ArrayList<TimelineItem>()
|
||||
for (index in timelineItemsCache.indices.reversed()) {
|
||||
val cacheItem = timelineItemsCache[index]
|
||||
if (cacheItem == null) {
|
||||
buildAndCacheItem(timelineItems, index)?.also { timelineItemState ->
|
||||
newTimelineItemStates.add(timelineItemState)
|
||||
}
|
||||
} else {
|
||||
newTimelineItemStates.add(cacheItem)
|
||||
}
|
||||
}
|
||||
this.timelineItems.emit(newTimelineItemStates)
|
||||
}
|
||||
|
||||
private fun calculateAndApplyDiff(newTimelineItems: List<MatrixTimelineItem>) {
|
||||
val timeToDiff = measureTimeMillis {
|
||||
val diffCallback =
|
||||
MatrixTimelineItemsDiffCallback(
|
||||
oldList = matrixTimelineItems,
|
||||
newList = newTimelineItems
|
||||
)
|
||||
val diffResult = DiffUtil.calculateDiff(diffCallback, false)
|
||||
matrixTimelineItems = newTimelineItems
|
||||
diffResult.dispatchUpdatesTo(cacheInvalidator)
|
||||
}
|
||||
Timber.v("Time to apply diff on new list of ${newTimelineItems.size} items: $timeToDiff ms")
|
||||
}
|
||||
|
||||
private suspend fun buildAndCacheItem(
|
||||
timelineItems: List<MatrixTimelineItem>,
|
||||
index: Int
|
||||
): TimelineItem? {
|
||||
val timelineItemState =
|
||||
when (val currentTimelineItem = timelineItems[index]) {
|
||||
is MatrixTimelineItem.Event -> eventItemFactory.create(currentTimelineItem, index, timelineItems)
|
||||
is MatrixTimelineItem.Virtual -> virtualItemFactory.create(currentTimelineItem, index, timelineItems)
|
||||
MatrixTimelineItem.Other -> null
|
||||
}
|
||||
timelineItemsCache[index] = timelineItemState
|
||||
return timelineItemState
|
||||
}
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ sealed interface TimelineItem {
|
|||
val id: String
|
||||
) : TimelineItem
|
||||
|
||||
data class MessageEvent(
|
||||
data class Event(
|
||||
val id: EventId,
|
||||
val senderId: String,
|
||||
val senderDisplayName: String?,
|
||||
|
|
|
|||
|
|
@ -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.features.messages.timeline.util
|
||||
|
||||
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 ->
|
||||
Jsoup.parse(formattedBody)
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,6 @@ object MatrixModule {
|
|||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun providesRustAuthenticationService(baseDirectory: File): AuthenticationService {
|
||||
return AuthenticationService(baseDirectory.absolutePath)
|
||||
return AuthenticationService(baseDirectory.absolutePath, null)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,6 +158,12 @@ internal class RustRoomSummaryDataSource(
|
|||
clear()
|
||||
addAll(diff.values.map { buildSummaryForRoomListEntry(it) })
|
||||
}
|
||||
SlidingSyncViewRoomsListDiff.Pop -> {
|
||||
removeLastOrNull()
|
||||
}
|
||||
SlidingSyncViewRoomsListDiff.Clear -> {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class RoomMessageFactory {
|
|||
eventId = EventId(eventTimelineItem.eventId() ?: ""),
|
||||
body = eventTimelineItem.content().asMessage()?.body() ?: "",
|
||||
sender = UserId(eventTimelineItem.sender()),
|
||||
originServerTs = eventTimelineItem.originServerTs()?.toLong() ?: 0L
|
||||
originServerTs = eventTimelineItem.timestamp().toLong()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import org.matrix.rustcomponents.sdk.TimelineListener
|
|||
|
||||
interface MatrixTimeline {
|
||||
var callback: Callback?
|
||||
val hasMoreToLoad: Boolean
|
||||
|
||||
interface Callback {
|
||||
fun onUpdatedTimelineItem(timelineItem: MatrixTimelineItem) = Unit
|
||||
|
|
@ -30,7 +29,7 @@ interface MatrixTimeline {
|
|||
}
|
||||
|
||||
fun timelineItems(): Flow<List<MatrixTimelineItem>>
|
||||
suspend fun paginateBackwards(count: Int): Result<Unit>
|
||||
suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result<Unit>
|
||||
fun addListener(timelineListener: TimelineListener)
|
||||
fun initialize()
|
||||
fun dispose()
|
||||
|
|
|
|||
|
|
@ -18,15 +18,10 @@ package io.element.android.libraries.matrix.timeline
|
|||
|
||||
import org.matrix.rustcomponents.sdk.EventTimelineItem
|
||||
import org.matrix.rustcomponents.sdk.TimelineItem
|
||||
import org.matrix.rustcomponents.sdk.TimelineKey
|
||||
|
||||
sealed interface MatrixTimelineItem {
|
||||
data class Event(val event: EventTimelineItem) : MatrixTimelineItem {
|
||||
val uniqueId: String
|
||||
get() = when (val eventKey = event.key()) {
|
||||
is TimelineKey.TransactionId -> eventKey.txnId
|
||||
is TimelineKey.EventId -> eventKey.eventId
|
||||
}
|
||||
val uniqueId: String = event.uniqueIdentifier()
|
||||
}
|
||||
|
||||
object Virtual : MatrixTimelineItem
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.timeline
|
|||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.core.EventId
|
||||
import io.element.android.libraries.matrix.room.RustMatrixRoom
|
||||
import io.element.android.libraries.matrix.util.StoppableSpawnBag
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -26,14 +27,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.PaginationOutcome
|
||||
import org.matrix.rustcomponents.sdk.PaginationOptions
|
||||
import org.matrix.rustcomponents.sdk.Room
|
||||
import org.matrix.rustcomponents.sdk.SlidingSyncRoom
|
||||
import org.matrix.rustcomponents.sdk.TimelineChange
|
||||
import org.matrix.rustcomponents.sdk.TimelineDiff
|
||||
import org.matrix.rustcomponents.sdk.TimelineListener
|
||||
import timber.log.Timber
|
||||
import java.util.Collections
|
||||
import java.util.*
|
||||
|
||||
class RustMatrixTimeline(
|
||||
private val matrixRoom: RustMatrixRoom,
|
||||
|
|
@ -45,20 +46,16 @@ class RustMatrixTimeline(
|
|||
|
||||
override var callback: MatrixTimeline.Callback? = null
|
||||
|
||||
private val paginationOutcome = MutableStateFlow(PaginationOutcome(true))
|
||||
private val timelineItems: MutableStateFlow<List<MatrixTimelineItem>> =
|
||||
MutableStateFlow(emptyList())
|
||||
|
||||
private val listenerTokens = StoppableSpawnBag()
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
override fun timelineItems(): Flow<List<MatrixTimelineItem>> {
|
||||
return timelineItems.sample(50)
|
||||
}
|
||||
|
||||
override val hasMoreToLoad: Boolean
|
||||
get() {
|
||||
return paginationOutcome.value.moreMessages
|
||||
}
|
||||
|
||||
private fun MutableList<MatrixTimelineItem>.applyDiff(diff: TimelineDiff) {
|
||||
when (diff.change()) {
|
||||
TimelineChange.PUSH -> {
|
||||
|
|
@ -107,12 +104,13 @@ class RustMatrixTimeline(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun paginateBackwards(count: Int): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||
if (!paginationOutcome.value.moreMessages) {
|
||||
return@withContext Result.failure(IllegalStateException("no more message"))
|
||||
}
|
||||
override suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result<Unit> = withContext(coroutineDispatchers.io) {
|
||||
runCatching {
|
||||
paginationOutcome.value = room.paginateBackwards(count.toUShort())
|
||||
val paginationOptions = PaginationOptions.UntilNumItems(
|
||||
eventLimit = requestSize.toUShort(),
|
||||
items = untilNumberOfItems.toUShort()
|
||||
)
|
||||
room.paginateBackwards(paginationOptions)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +122,7 @@ class RustMatrixTimeline(
|
|||
}
|
||||
|
||||
override fun addListener(timelineListener: TimelineListener) {
|
||||
slidingSyncRoom.addTimelineListener(timelineListener)
|
||||
listenerTokens += slidingSyncRoom.subscribeAndAddTimelineListener(timelineListener, settings = null)
|
||||
}
|
||||
|
||||
override fun initialize() {
|
||||
|
|
@ -132,7 +130,7 @@ class RustMatrixTimeline(
|
|||
}
|
||||
|
||||
override fun dispose() {
|
||||
slidingSyncRoom.removeTimeline()
|
||||
listenerTokens.dispose()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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.util
|
||||
|
||||
import org.matrix.rustcomponents.sdk.StoppableSpawn
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
|
||||
class StoppableSpawnBag(private val tokens: MutableSet<StoppableSpawn> = CopyOnWriteArraySet()) : Set<StoppableSpawn> by tokens {
|
||||
|
||||
operator fun plusAssign(stoppableSpawn: StoppableSpawn?) {
|
||||
if (stoppableSpawn == null) return
|
||||
tokens += stoppableSpawn
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
tokens.forEach { it.cancel() }
|
||||
tokens.clear()
|
||||
}
|
||||
}
|
||||
|
|
@ -29,14 +29,11 @@ class FakeMatrixTimeline : MatrixTimeline {
|
|||
get() = null
|
||||
set(value) {}
|
||||
|
||||
override val hasMoreToLoad: Boolean
|
||||
get() = true
|
||||
|
||||
override fun timelineItems(): Flow<List<MatrixTimelineItem>> {
|
||||
return emptyFlow()
|
||||
}
|
||||
|
||||
override suspend fun paginateBackwards(count: Int): Result<Unit> {
|
||||
override suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result<Unit> {
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue