|
|
@ -10,11 +10,15 @@ import io.element.android.x.features.messages.model.MessagesItemGroupPosition |
|
|
|
import io.element.android.x.features.messages.model.MessagesItemReactionState |
|
|
|
import io.element.android.x.features.messages.model.MessagesItemReactionState |
|
|
|
import io.element.android.x.features.messages.model.MessagesTimelineItemState |
|
|
|
import io.element.android.x.features.messages.model.MessagesTimelineItemState |
|
|
|
import io.element.android.x.features.messages.model.content.* |
|
|
|
import io.element.android.x.features.messages.model.content.* |
|
|
|
|
|
|
|
import io.element.android.x.features.messages.util.invalidateLast |
|
|
|
import io.element.android.x.matrix.MatrixClient |
|
|
|
import io.element.android.x.matrix.MatrixClient |
|
|
|
import io.element.android.x.matrix.media.MediaResolver |
|
|
|
import io.element.android.x.matrix.media.MediaResolver |
|
|
|
import io.element.android.x.matrix.room.MatrixRoom |
|
|
|
import io.element.android.x.matrix.room.MatrixRoom |
|
|
|
import io.element.android.x.matrix.timeline.MatrixTimelineItem |
|
|
|
import io.element.android.x.matrix.timeline.MatrixTimelineItem |
|
|
|
import kotlinx.coroutines.CoroutineDispatcher |
|
|
|
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.Mutex |
|
|
|
import kotlinx.coroutines.sync.withLock |
|
|
|
import kotlinx.coroutines.sync.withLock |
|
|
|
import kotlinx.coroutines.withContext |
|
|
|
import kotlinx.coroutines.withContext |
|
|
@ -32,51 +36,66 @@ class MessageTimelineItemStateFactory( |
|
|
|
private val dispatcher: CoroutineDispatcher, |
|
|
|
private val dispatcher: CoroutineDispatcher, |
|
|
|
) { |
|
|
|
) { |
|
|
|
|
|
|
|
|
|
|
|
private val timelineItemCaches = arrayListOf<MessagesTimelineItemState?>() |
|
|
|
private val timelineItemStates = MutableStateFlow<List<MessagesTimelineItemState>>(emptyList()) |
|
|
|
private var currentSnapshot: List<MatrixTimelineItem> = emptyList() |
|
|
|
private val timelineItemStatesCache = arrayListOf<MessagesTimelineItemState?>() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Items from rust sdk, used for diffing |
|
|
|
|
|
|
|
private var timelineItems: List<MatrixTimelineItem> = emptyList() |
|
|
|
|
|
|
|
|
|
|
|
private val lock = Mutex() |
|
|
|
private val lock = Mutex() |
|
|
|
private val cacheInvalidator = CacheInvalidator(timelineItemCaches) |
|
|
|
private val cacheInvalidator = CacheInvalidator(timelineItemStatesCache) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fun flow(): StateFlow<List<MessagesTimelineItemState>> = timelineItemStates.asStateFlow() |
|
|
|
|
|
|
|
|
|
|
|
suspend fun create( |
|
|
|
suspend fun replaceWith( |
|
|
|
timelineItems: List<MatrixTimelineItem>, |
|
|
|
timelineItems: List<MatrixTimelineItem>, |
|
|
|
): List<MessagesTimelineItemState> = |
|
|
|
) = withContext(dispatcher) { |
|
|
|
withContext(dispatcher) { |
|
|
|
lock.withLock { |
|
|
|
lock.withLock { |
|
|
|
calculateAndApplyDiff(timelineItems) |
|
|
|
calculateAndApplyDiff(timelineItems) |
|
|
|
buildAndEmitTimelineItemStates(timelineItems) |
|
|
|
getOrCreateFromCache(timelineItems) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private suspend fun getOrCreateFromCache(timelineItems: List<MatrixTimelineItem>): List<MessagesTimelineItemState> { |
|
|
|
suspend fun pushItem( |
|
|
|
val messagesTimelineItemState = ArrayList<MessagesTimelineItemState>() |
|
|
|
timelineItem: MatrixTimelineItem, |
|
|
|
for (index in timelineItemCaches.indices.reversed()) { |
|
|
|
) = withContext(dispatcher) { |
|
|
|
val cacheItem = timelineItemCaches[index] |
|
|
|
lock.withLock { |
|
|
|
|
|
|
|
// Makes sure to invalidate last as we need to recompute some data (like groupPosition) |
|
|
|
|
|
|
|
timelineItemStatesCache.invalidateLast() |
|
|
|
|
|
|
|
timelineItemStatesCache.add(null) |
|
|
|
|
|
|
|
timelineItems = timelineItems + timelineItem |
|
|
|
|
|
|
|
buildAndEmitTimelineItemStates(timelineItems) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private suspend fun buildAndEmitTimelineItemStates(timelineItems: List<MatrixTimelineItem>) { |
|
|
|
|
|
|
|
val newTimelineItemStates = ArrayList<MessagesTimelineItemState>() |
|
|
|
|
|
|
|
for (index in timelineItemStatesCache.indices.reversed()) { |
|
|
|
|
|
|
|
val cacheItem = timelineItemStatesCache[index] |
|
|
|
if (cacheItem == null) { |
|
|
|
if (cacheItem == null) { |
|
|
|
buildAndCacheItem(timelineItems, index)?.also { timelineItemState -> |
|
|
|
buildAndCacheItem(timelineItems, index)?.also { timelineItemState -> |
|
|
|
messagesTimelineItemState.add(timelineItemState) |
|
|
|
newTimelineItemStates.add(timelineItemState) |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
messagesTimelineItemState.add(cacheItem) |
|
|
|
newTimelineItemStates.add(cacheItem) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return messagesTimelineItemState |
|
|
|
timelineItemStates.emit(newTimelineItemStates) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun calculateAndApplyDiff(timelineItems: List<MatrixTimelineItem>) { |
|
|
|
private fun calculateAndApplyDiff(newTimelineItems: List<MatrixTimelineItem>) { |
|
|
|
val timeToDiff = measureTimeMillis { |
|
|
|
val timeToDiff = measureTimeMillis { |
|
|
|
val diffCallback = |
|
|
|
val diffCallback = |
|
|
|
MatrixTimelineItemsDiffCallback( |
|
|
|
MatrixTimelineItemsDiffCallback( |
|
|
|
oldList = currentSnapshot, |
|
|
|
oldList = timelineItems, |
|
|
|
newList = timelineItems |
|
|
|
newList = newTimelineItems |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
val diffResult = DiffUtil.calculateDiff(diffCallback, false) |
|
|
|
val diffResult = DiffUtil.calculateDiff(diffCallback, false) |
|
|
|
currentSnapshot = timelineItems |
|
|
|
timelineItems = newTimelineItems |
|
|
|
diffResult.dispatchUpdatesTo(cacheInvalidator) |
|
|
|
diffResult.dispatchUpdatesTo(cacheInvalidator) |
|
|
|
} |
|
|
|
} |
|
|
|
Timber.v("Time to apply diff on new list of ${timelineItems.size} items: $timeToDiff ms") |
|
|
|
Timber.v("Time to apply diff on new list of ${newTimelineItems.size} items: $timeToDiff ms") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private suspend fun buildAndCacheItem( |
|
|
|
private suspend fun buildAndCacheItem( |
|
|
@ -97,7 +116,7 @@ class MessageTimelineItemStateFactory( |
|
|
|
) |
|
|
|
) |
|
|
|
MatrixTimelineItem.Other -> null |
|
|
|
MatrixTimelineItem.Other -> null |
|
|
|
} |
|
|
|
} |
|
|
|
timelineItemCaches[index] = timelineItemState |
|
|
|
timelineItemStatesCache[index] = timelineItemState |
|
|
|
return timelineItemState |
|
|
|
return timelineItemState |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -213,4 +232,5 @@ class MessageTimelineItemStateFactory( |
|
|
|
.resolve(url, kind = MediaResolver.Kind.Thumbnail(size.value)) |
|
|
|
.resolve(url, kind = MediaResolver.Kind.Thumbnail(size.value)) |
|
|
|
return AvatarData(name, model, size) |
|
|
|
return AvatarData(name, model, size) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|