mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-16 20:10:04 +03:00
Timeline: handle an in memory local echo to make the UI snappier
This commit is contained in:
parent
9fc3fa7f19
commit
5e1b59f9d3
9 changed files with 66 additions and 23 deletions
|
@ -106,6 +106,10 @@ class CommonTestHelper(context: Context) {
|
|||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
}
|
||||
|
||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||
//noop
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
// TODO Count only new messages?
|
||||
if (snapshot.count { it.root.type == EventType.MESSAGE } == nbOfMessages) {
|
||||
|
|
|
@ -114,7 +114,7 @@ interface Timeline {
|
|||
fun onTimelineFailure(throwable: Throwable)
|
||||
|
||||
/**
|
||||
* Call when new events come through the sync
|
||||
* Called when new events come through the sync
|
||||
*/
|
||||
fun onNewTimelineEvents(eventIds: List<String>)
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventConten
|
|||
data class TimelineEvent(
|
||||
val root: Event,
|
||||
val localId: Long,
|
||||
val eventId: String,
|
||||
val displayIndex: Int,
|
||||
val senderName: String?,
|
||||
val isUniqueDisplayName: Boolean,
|
||||
|
|
|
@ -37,6 +37,7 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS
|
|||
return TimelineEvent(
|
||||
root = timelineEventEntity.root?.asDomain()
|
||||
?: Event("", timelineEventEntity.eventId),
|
||||
eventId = timelineEventEntity.eventId,
|
||||
annotations = timelineEventEntity.annotations?.asDomain(),
|
||||
localId = timelineEventEntity.localId,
|
||||
displayIndex = timelineEventEntity.displayIndex,
|
||||
|
|
|
@ -56,6 +56,7 @@ import im.vector.matrix.android.internal.util.StringProvider
|
|||
import kotlinx.coroutines.launch
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -419,7 +420,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
fun createLocalEcho(event: Event){
|
||||
fun createLocalEcho(event: Event) {
|
||||
checkNotNull(event.roomId) { "Your event should have a roomId" }
|
||||
taskExecutor.executorScope.launch {
|
||||
localEchoRepository.createLocalEcho(event)
|
||||
|
|
|
@ -24,13 +24,17 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
|||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.internal.database.helper.addTimelineEvent
|
||||
import im.vector.matrix.android.internal.database.helper.nextId
|
||||
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.mapper.toEntity
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates
|
||||
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
|
||||
|
@ -39,24 +43,27 @@ import im.vector.matrix.android.internal.util.awaitTransaction
|
|||
import io.realm.Realm
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
import java.lang.IllegalStateException
|
||||
import javax.inject.Inject
|
||||
import kotlin.random.Random
|
||||
|
||||
internal class LocalEchoRepository @Inject constructor(private val monarchy: Monarchy,
|
||||
private val roomSummaryUpdater: RoomSummaryUpdater,
|
||||
private val eventBus: EventBus) {
|
||||
private val eventBus: EventBus,
|
||||
private val timelineEventMapper: TimelineEventMapper) {
|
||||
|
||||
suspend fun createLocalEcho(event: Event) {
|
||||
val roomId = event.roomId ?: return
|
||||
val senderId = event.senderId ?: return
|
||||
val eventId = event.eventId ?: return
|
||||
eventBus.post(DefaultTimeline.OnNewTimelineEvents(roomId = roomId, eventIds = listOf(eventId)))
|
||||
monarchy.awaitTransaction { realm ->
|
||||
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@awaitTransaction
|
||||
val roomId = event.roomId ?: throw IllegalStateException("You should have set a roomId for your event")
|
||||
val senderId = event.senderId ?: throw IllegalStateException("You should have set a senderIf for your event")
|
||||
if (event.eventId == null) {
|
||||
throw IllegalStateException("You should have set an eventId for your event")
|
||||
}
|
||||
val timelineEventEntity = Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
val eventEntity = event.toEntity(roomId, SendState.UNSENT)
|
||||
val roomMemberHelper = RoomMemberHelper(realm, roomId)
|
||||
val myUser = roomMemberHelper.getLastRoomMember(senderId)
|
||||
val localId = TimelineEventEntity.nextId(realm)
|
||||
val timelineEventEntity = TimelineEventEntity(localId).also {
|
||||
TimelineEventEntity(localId).also {
|
||||
it.root = eventEntity
|
||||
it.eventId = event.eventId
|
||||
it.roomId = roomId
|
||||
|
@ -64,6 +71,11 @@ internal class LocalEchoRepository @Inject constructor(private val monarchy: Mon
|
|||
it.senderAvatar = myUser?.avatarUrl
|
||||
it.isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName(myUser?.displayName)
|
||||
}
|
||||
}
|
||||
val timelineEvent = timelineEventMapper.map(timelineEventEntity)
|
||||
eventBus.post(DefaultTimeline.OnLocalEchoCreated(roomId = roomId, timelineEvent = timelineEvent))
|
||||
monarchy.awaitTransaction { realm ->
|
||||
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@awaitTransaction
|
||||
roomEntity.sendingTimelineEvents.add(0, timelineEventEntity)
|
||||
roomSummaryUpdater.update(realm, roomId)
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ internal class DefaultTimeline(
|
|||
) : Timeline, TimelineHiddenReadReceipts.Delegate {
|
||||
|
||||
data class OnNewTimelineEvents(val roomId: String, val eventIds: List<String>)
|
||||
data class OnLocalEchoCreated(val roomId: String, val timelineEvent: TimelineEvent)
|
||||
|
||||
companion object {
|
||||
val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD")
|
||||
|
@ -99,6 +100,7 @@ internal class DefaultTimeline(
|
|||
|
||||
private var prevDisplayIndex: Int? = null
|
||||
private var nextDisplayIndex: Int? = null
|
||||
private val inMemorySendingEvents = Collections.synchronizedList<TimelineEvent>(ArrayList())
|
||||
private val builtEvents = Collections.synchronizedList<TimelineEvent>(ArrayList())
|
||||
private val builtEventsIdMap = Collections.synchronizedMap(HashMap<String, Int>())
|
||||
private val backwardsState = AtomicReference(State())
|
||||
|
@ -321,13 +323,24 @@ internal class DefaultTimeline(
|
|||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onNewTimelineEvents(onNewTimelineEvents: OnNewTimelineEvents) {
|
||||
if (onNewTimelineEvents.roomId == roomId) {
|
||||
if (isLive && onNewTimelineEvents.roomId == roomId) {
|
||||
listeners.forEach {
|
||||
it.onNewTimelineEvents(onNewTimelineEvents.eventIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onLocalEchoCreated(onLocalEchoCreated: OnLocalEchoCreated) {
|
||||
if (isLive && onLocalEchoCreated.roomId == roomId) {
|
||||
listeners.forEach {
|
||||
it.onNewTimelineEvents(listOf(onLocalEchoCreated.timelineEvent.eventId))
|
||||
}
|
||||
inMemorySendingEvents.add(0, onLocalEchoCreated.timelineEvent)
|
||||
postSnapshot()
|
||||
}
|
||||
}
|
||||
|
||||
// Private methods *****************************************************************************
|
||||
|
||||
private fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent): Boolean {
|
||||
|
@ -394,12 +407,15 @@ internal class DefaultTimeline(
|
|||
private fun buildSendingEvents(): List<TimelineEvent> {
|
||||
val sendingEvents = ArrayList<TimelineEvent>()
|
||||
if (hasReachedEnd(Timeline.Direction.FORWARDS) && !hasMoreInCache(Timeline.Direction.FORWARDS)) {
|
||||
sendingEvents.addAll(inMemorySendingEvents)
|
||||
roomEntity?.sendingTimelineEvents
|
||||
?.where()
|
||||
?.filterEventsWithSettings()
|
||||
?.findAll()
|
||||
?.forEach {
|
||||
sendingEvents.add(timelineEventMapper.map(it))
|
||||
?.forEach { timelineEventEntity ->
|
||||
if (sendingEvents.find { it.eventId == timelineEventEntity.eventId } == null) {
|
||||
sendingEvents.add(timelineEventMapper.map(timelineEventEntity))
|
||||
}
|
||||
}
|
||||
}
|
||||
return sendingEvents
|
||||
|
@ -580,6 +596,11 @@ internal class DefaultTimeline(
|
|||
offsetResults.forEach { eventEntity ->
|
||||
|
||||
val timelineEvent = buildTimelineEvent(eventEntity)
|
||||
val transactionId = timelineEvent.root.unsignedData?.transactionId
|
||||
val sendingEvent = inMemorySendingEvents.find {
|
||||
it.eventId == transactionId
|
||||
}
|
||||
inMemorySendingEvents.remove(sendingEvent)
|
||||
|
||||
if (timelineEvent.isEncrypted()
|
||||
&& timelineEvent.root.mxDecryptionResult == null) {
|
||||
|
@ -665,7 +686,7 @@ internal class DefaultTimeline(
|
|||
it.onTimelineUpdated(snapshot)
|
||||
}
|
||||
}
|
||||
debouncer.debounce("post_snapshot", runnable, 50)
|
||||
debouncer.debounce("post_snapshot", runnable, 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,26 +21,30 @@ import im.vector.riotx.core.platform.DefaultListUpdateCallback
|
|||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.BaseEventItem
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager,
|
||||
private val timelineEventController: TimelineEventController) : DefaultListUpdateCallback {
|
||||
|
||||
private val newTimelineEventIds = HashSet<String>()
|
||||
private val newTimelineEventIds = CopyOnWriteArrayList<String>()
|
||||
|
||||
fun addNewTimelineEventIds(eventIds: List<String>){
|
||||
newTimelineEventIds.addAll(eventIds)
|
||||
fun addNewTimelineEventIds(eventIds: List<String>) {
|
||||
newTimelineEventIds.addAll(0, eventIds)
|
||||
}
|
||||
|
||||
override fun onInserted(position: Int, count: Int) {
|
||||
Timber.v("On inserted $count count at position: $position")
|
||||
if(layoutManager.findFirstVisibleItemPosition() != position ){
|
||||
if (layoutManager.findFirstVisibleItemPosition() != position) {
|
||||
return
|
||||
}
|
||||
val firstNewItem = timelineEventController.adapter.getModelAtPosition(position) as? BaseEventItem ?: return
|
||||
val firstNewItemIds = firstNewItem.getEventIds()
|
||||
if(newTimelineEventIds.intersect(firstNewItemIds).isNotEmpty()){
|
||||
val firstNewItemIds = firstNewItem.getEventIds().firstOrNull()
|
||||
val indexOfFirstNewItem = newTimelineEventIds.indexOf(firstNewItemIds)
|
||||
if (indexOfFirstNewItem != -1) {
|
||||
Timber.v("Should scroll to position: $position")
|
||||
newTimelineEventIds.clear()
|
||||
repeat(newTimelineEventIds.size - indexOfFirstNewItem) {
|
||||
newTimelineEventIds.removeAt(indexOfFirstNewItem)
|
||||
}
|
||||
layoutManager.scrollToPosition(position)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import im.vector.matrix.android.api.session.room.timeline.Timeline
|
|||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotx.core.date.VectorDateFormatter
|
||||
import im.vector.riotx.core.epoxy.LoadingItem_
|
||||
import im.vector.riotx.core.epoxy.emptyItem
|
||||
import im.vector.riotx.core.extensions.localDateTime
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailViewState
|
||||
import im.vector.riotx.features.home.room.detail.UnreadState
|
||||
|
@ -253,7 +252,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
currentSnapshot = newSnapshot
|
||||
val diffResult = DiffUtil.calculateDiff(diffCallback)
|
||||
diffResult.dispatchUpdatesTo(listUpdateCallback)
|
||||
requestDelayedModelBuild(100)
|
||||
requestModelBuild()
|
||||
inSubmitList = false
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue