Merge pull request #334 from vector-im/feature/general_perf

Feature/general perf
This commit is contained in:
ganfra 2019-07-11 15:52:00 +02:00 committed by GitHub
commit 252b2ea30a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 121 additions and 115 deletions

View File

@ -19,4 +19,4 @@ package im.vector.matrix.android
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.Dispatchers.Main

internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main)
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main)

View File

@ -30,7 +30,7 @@ import im.vector.matrix.android.api.session.room.send.SendState
*/
data class TimelineEvent(
val root: Event,
val localId: String,
val localId: Long,
val displayIndex: Int,
val senderName: String?,
val isUniqueDisplayName: Boolean,

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.database
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.util.createBackgroundHandler
import io.realm.OrderedCollectionChangeSet
import io.realm.OrderedRealmCollectionChangeListener
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.RealmObject
@ -33,7 +34,7 @@ internal interface LiveEntityObserver {
}

internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val realmConfiguration: RealmConfiguration)
: LiveEntityObserver {
: LiveEntityObserver, OrderedRealmCollectionChangeListener<RealmResults<T>> {

private companion object {
val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND")
@ -50,9 +51,7 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val r
val realm = Realm.getInstance(realmConfiguration)
backgroundRealm.set(realm)
val queryResults = query.createQuery(realm).findAll()
queryResults.addChangeListener { t, changeSet ->
onChanged(t, changeSet)
}
queryResults.addChangeListener(this)
results = AtomicReference(queryResults)
}
}
@ -73,19 +72,4 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val r
return isStarted.get()
}

private fun onChanged(realmResults: RealmResults<T>, changeSet: OrderedCollectionChangeSet) {
val insertionIndexes = changeSet.insertions
val updateIndexes = changeSet.changes
val deletionIndexes = changeSet.deletions
val inserted = realmResults.filterIndexed { index, _ -> insertionIndexes.contains(index) }
val updated = realmResults.filterIndexed { index, _ -> updateIndexes.contains(index) }
val deleted = realmResults.filterIndexed { index, _ -> deletionIndexes.contains(index) }
processChanges(inserted, updated, deleted)
}

/**
* Do quick treatment or delegate on a task
*/
protected abstract fun processChanges(inserted: List<T>, updated: List<T>, deleted: List<T>)

}

View File

@ -134,7 +134,8 @@ internal fun ChunkEntity.add(roomId: String,
}
}

val eventEntity = TimelineEventEntity().also {
val localId = TimelineEventEntity.nextId(realm)
val eventEntity = TimelineEventEntity(localId).also {
it.root = event.toEntity(roomId).apply {
this.stateIndex = currentStateIndex
this.isUnlinked = isUnlinked

View File

@ -26,7 +26,6 @@ import im.vector.matrix.android.internal.database.query.fastContains
import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.room.membership.RoomMembers


internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
chunks.remove(chunkEntity)
chunkEntity.deleteOnCascade()
@ -65,7 +64,8 @@ internal fun RoomEntity.addSendingEvent(event: Event) {
}
val roomMembers = RoomMembers(realm, roomId)
val myUser = roomMembers.get(senderId)
val timelineEventEntity = TimelineEventEntity().also {
val localId = TimelineEventEntity.nextId(realm)
val timelineEventEntity = TimelineEventEntity(localId).also {
it.root = eventEntity
it.eventId = event.eventId ?: ""
it.roomId = roomId

View File

@ -30,9 +30,11 @@ import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmQuery


internal fun TimelineEventEntity.updateSenderData() {
assertIsManaged()
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return
@ -69,6 +71,15 @@ internal fun TimelineEventEntity.updateSenderData() {
this.senderMembershipEvent = senderMembershipEvent
}

internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long{
val currentIdNum = realm.where(TimelineEventEntity::class.java).max(TimelineEventEntityFields.LOCAL_ID)
return if (currentIdNum == null) {
1
} else {
currentIdNum.toLong() + 1
}
}

private fun RealmList<TimelineEventEntity>.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery<TimelineEventEntity> {
return where()
.equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender)

View File

@ -35,7 +35,7 @@ internal object EventMapper {
val uds = if (event.unsignedData == null) null
else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(event.unsignedData)
val eventEntity = EventEntity()
eventEntity.eventId = event.eventId ?: UUID.randomUUID().toString()
eventEntity.eventId = event.eventId ?: ""
eventEntity.roomId = event.roomId ?: roomId
eventEntity.content = ContentMapper.map(event.content)
val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent

View File

@ -23,15 +23,6 @@ import im.vector.matrix.android.internal.database.model.TimelineEventEntity

internal object TimelineEventMapper {

fun map(timelineEvent: TimelineEvent, roomId: String): TimelineEventEntity {
val timelineEventEntity = TimelineEventEntity()
timelineEventEntity.root = timelineEvent.root.toEntity(roomId)
timelineEventEntity.eventId = timelineEvent.root.eventId ?: ""
timelineEventEntity.roomId = roomId
timelineEventEntity.annotations = timelineEvent.annotations?.let { EventAnnotationsSummaryMapper.map(it, roomId) }
return timelineEventEntity
}

fun map(timelineEventEntity: TimelineEventEntity): TimelineEvent {

return TimelineEvent(
@ -53,8 +44,4 @@ internal fun TimelineEventEntity.asDomain(): TimelineEvent {
return TimelineEventMapper.map(this)
}

internal fun TimelineEvent.toEntity(roomId: String): TimelineEventEntity {
return TimelineEventMapper.map(this, roomId)
}



View File

@ -27,8 +27,7 @@ import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import java.util.*

internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUID().toString(),
@Index var eventId: String = "",
internal open class EventEntity(@Index var eventId: String = "",
@Index var roomId: String = "",
@Index var type: String = "",
var content: String? = null,

View File

@ -16,10 +16,6 @@

package im.vector.matrix.android.internal.database.model

import com.squareup.moshi.Types
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.di.MoshiProvider
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Index
@ -28,7 +24,7 @@ import io.realm.annotations.PrimaryKey
import java.util.*


internal open class TimelineEventEntity(@PrimaryKey var localId: String = UUID.randomUUID().toString(),
internal open class TimelineEventEntity(var localId: Long = 0,
@Index var eventId: String = "",
@Index var roomId: String = "",
var root: EventEntity? = null,

View File

@ -18,15 +18,15 @@ package im.vector.matrix.android.internal.di

import android.content.Context
import android.content.res.Resources
import android.os.Handler
import android.os.HandlerThread
import dagger.Module
import dagger.Provides
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.createBackgroundHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import org.matrix.olm.OlmManager
import java.util.concurrent.Executors

@Module
internal object MatrixModule {
@ -38,7 +38,8 @@ internal object MatrixModule {
return MatrixCoroutineDispatchers(io = Dispatchers.IO,
computation = Dispatchers.IO,
main = Dispatchers.Main,
crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher()
crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(),
sync = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
)
}


View File

@ -20,6 +20,7 @@ import android.content.Context
import android.os.Looper
import androidx.annotation.MainThread
import androidx.lifecycle.LiveData
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.PushRuleService
@ -52,34 +53,34 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
private val context: Context,
private val liveEntityObservers: Set<@JvmSuppressWildcards LiveEntityObserver>,
private val sessionListeners: SessionListeners,
private val roomService: RoomService,
private val roomDirectoryService: RoomDirectoryService,
private val groupService: GroupService,
private val userService: UserService,
private val filterService: FilterService,
private val cacheService: CacheService,
private val signOutService: SignOutService,
private val pushRuleService: PushRuleService,
private val pushersService: PushersService,
private val cryptoService: CryptoManager,
private val fileService: FileService,
private val roomService: Lazy<RoomService>,
private val roomDirectoryService: Lazy<RoomDirectoryService>,
private val groupService: Lazy<GroupService>,
private val userService: Lazy<UserService>,
private val filterService: Lazy<FilterService>,
private val cacheService: Lazy<CacheService>,
private val signOutService: Lazy<SignOutService>,
private val pushRuleService: Lazy<PushRuleService>,
private val pushersService: Lazy<PushersService>,
private val cryptoService: Lazy<CryptoManager>,
private val fileService: Lazy<FileService>,
private val syncThread: SyncThread,
private val contentUrlResolver: ContentUrlResolver,
private val contentUploadProgressTracker: ContentUploadStateTracker,
private val initialSyncProgressService: InitialSyncProgressService)
private val initialSyncProgressService: Lazy<InitialSyncProgressService>)
: Session,
RoomService by roomService,
RoomDirectoryService by roomDirectoryService,
GroupService by groupService,
UserService by userService,
CryptoService by cryptoService,
CacheService by cacheService,
SignOutService by signOutService,
FilterService by filterService,
PushRuleService by pushRuleService,
PushersService by pushersService,
FileService by fileService,
InitialSyncProgressService by initialSyncProgressService {
RoomService by roomService.get(),
RoomDirectoryService by roomDirectoryService.get(),
GroupService by groupService.get(),
UserService by userService.get(),
CryptoService by cryptoService.get(),
CacheService by cacheService.get(),
SignOutService by signOutService.get(),
FilterService by filterService.get(),
PushRuleService by pushRuleService.get(),
PushersService by pushersService.get(),
FileService by fileService.get(),
InitialSyncProgressService by initialSyncProgressService.get() {

private var isOpen = false

@ -123,7 +124,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
assert(isOpen)
stopSync()
liveEntityObservers.forEach { it.dispose() }
cryptoService.close()
cryptoService.get().close()
isOpen = false
}

@ -140,15 +141,15 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
//syncThread.kill()

Timber.w("SIGN_OUT: call webservice")
return signOutService.signOut(object : MatrixCallback<Unit> {
return signOutService.get().signOut(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.w("SIGN_OUT: call webservice -> SUCCESS: clear cache")

// Clear the cache
cacheService.clearCache(object : MatrixCallback<Unit> {
cacheService.get().clearCache(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.w("SIGN_OUT: clear cache -> SUCCESS: clear crypto cache")
cryptoService.clearCryptoCache(MatrixCallbackDelegate(callback))
cryptoService.get().clearCryptoCache(MatrixCallbackDelegate(callback))

WorkManagerUtil.cancelAllWorks(context)
}

View File

@ -22,13 +22,20 @@ import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.GroupEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.session.room.prune.PruneEventTask
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.worker.WorkManagerUtil
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import io.realm.OrderedCollectionChangeSet
import io.realm.RealmConfiguration
import io.realm.RealmResults
import timber.log.Timber
import javax.inject.Inject

private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
@ -40,8 +47,12 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont

override val query = Monarchy.Query<GroupEntity> { GroupEntity.where(it) }

override fun processChanges(inserted: List<GroupEntity>, updated: List<GroupEntity>, deleted: List<GroupEntity>) {
val newGroupIds = inserted.map { it.groupId }
override fun onChange(results: RealmResults<GroupEntity>, changeSet: OrderedCollectionChangeSet) {
val newGroupIds = changeSet.insertions
.asSequence()
.mapNotNull { results[it]?.groupId}
.toList()

val getGroupDataWorkerParams = GetGroupDataWorker.Params(credentials.userId, newGroupIds)
val workData = WorkerParamsFactory.toData(getGroupDataWorkerParams)

@ -55,5 +66,4 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont
.enqueue()
}


}

View File

@ -25,7 +25,9 @@ import im.vector.matrix.android.internal.database.query.types
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import io.realm.OrderedCollectionChangeSet
import io.realm.RealmConfiguration
import io.realm.RealmResults
import timber.log.Timber
import javax.inject.Inject

@ -50,19 +52,18 @@ internal class EventRelationsAggregationUpdater @Inject constructor(@SessionData
)
}

override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
Timber.v("EventRelationsAggregationUpdater called with ${inserted.size} insertions")
val domainInserted = inserted
.map { it.asDomain() }
override fun onChange(results: RealmResults<EventEntity>, changeSet: OrderedCollectionChangeSet) {
Timber.v("EventRelationsAggregationUpdater called with ${changeSet.insertions.size} insertions")

val insertedDomains = changeSet.insertions
.asSequence()
.mapNotNull { results[it]?.asDomain() }
.toList()
val params = EventRelationsAggregationTask.Params(
domainInserted,
insertedDomains,
credentials.userId
)

task.configureWith(params)
.executeBy(taskExecutor)

task.configureWith(params).executeBy(taskExecutor)
}

}

View File

@ -26,9 +26,12 @@ import im.vector.matrix.android.internal.database.query.types
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationTask
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import io.realm.OrderedCollectionChangeSet
import io.realm.RealmConfiguration
import io.realm.RealmResults
import timber.log.Timber
import javax.inject.Inject

@ -44,18 +47,19 @@ internal class EventsPruner @Inject constructor(@SessionDatabase realmConfigurat

override val query = Monarchy.Query<EventEntity> { EventEntity.types(it, listOf(EventType.REDACTION)) }

override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
Timber.v("Event pruner called with ${inserted.size} insertions")
val redactionEvents = inserted.map { it.asDomain() }
override fun onChange(results: RealmResults<EventEntity>, changeSet: OrderedCollectionChangeSet) {
Timber.v("Event pruner called with ${changeSet.insertions.size} insertions")

val insertedDomains = changeSet.insertions
.asSequence()
.mapNotNull { results[it]?.asDomain() }
.toList()

val params = PruneEventTask.Params(
redactionEvents,
insertedDomains,
credentials.userId
)

pruneEventTask.configureWith(params)
.executeBy(taskExecutor)

pruneEventTask.configureWith(params).executeBy(taskExecutor)
}

}

View File

@ -43,7 +43,7 @@ import kotlin.collections.ArrayList
import kotlin.collections.HashMap


private const val INITIAL_LOAD_SIZE = 20
private const val INITIAL_LOAD_SIZE = 10
private const val MIN_FETCHING_COUNT = 30
private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE

@ -216,6 +216,7 @@ internal class DefaultTimeline(
backgroundRealm.set(realm)
clearUnlinkedEvents(realm)


roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()?.also {
it.sendingTimelineEvents.addChangeListener { _ ->
postSnapshot()
@ -242,6 +243,8 @@ internal class DefaultTimeline(
Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId")
BACKGROUND_HANDLER.post {
cancelableBag.cancel()
roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
eventRelations.removeAllChangeListeners()
liveEvents.removeAllChangeListeners()
backgroundRealm.getAndSet(null).also {
it.close()

View File

@ -105,8 +105,8 @@ open class SyncService : Service() {
Timber.v("Execute sync request with timeout 0")
val params = SyncTask.Params(TIME_OUT)
cancelableTask = syncTask.configureWith(params)
.callbackOn(TaskThread.CALLER)
.executeOn(TaskThread.CALLER)
.callbackOn(TaskThread.SYNC)
.executeOn(TaskThread.SYNC)
.dispatchTo(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
cancelableTask = null

View File

@ -97,8 +97,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
val latch = CountDownLatch(1)
val params = SyncTask.Params(DEFAULT_LONG_POOL_TIMEOUT)
cancelableTask = syncTask.configureWith(params)
.callbackOn(TaskThread.CALLER)
.executeOn(TaskThread.CALLER)
.callbackOn(TaskThread.SYNC)
.executeOn(TaskThread.SYNC)
.dispatchTo(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
latch.countDown()

View File

@ -62,8 +62,8 @@ internal class SyncWorker(context: Context,
val latch = CountDownLatch(1)
val taskParams = SyncTask.Params(0)
cancelableTask = syncTask.configureWith(taskParams)
.callbackOn(TaskThread.CALLER)
.executeOn(TaskThread.CALLER)
.callbackOn(TaskThread.SYNC)
.executeOn(TaskThread.SYNC)
.dispatchTo(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
latch.countDown()

View File

@ -22,13 +22,13 @@ import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.query.types
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith
import io.realm.OrderedCollectionChangeSet
import io.realm.RealmConfiguration
import io.realm.RealmResults
import io.realm.Sort
import javax.inject.Inject

@ -42,15 +42,19 @@ internal class UserEntityUpdater @Inject constructor(@SessionDatabase realmConfi
.types(it, listOf(EventType.STATE_ROOM_MEMBER))
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
.distinct(EventEntityFields.STATE_KEY)

}

override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
val roomMembersEvents = inserted.map { it.eventId }
override fun onChange(results: RealmResults<EventEntity>, changeSet: OrderedCollectionChangeSet) {
val roomMembersEvents = changeSet.insertions
.asSequence()
.mapNotNull { results[it]?.eventId }
.toList()

val taskParams = UpdateUserTask.Params(roomMembersEvents)
updateUserTask
.configureWith(taskParams)
.executeOn(TaskThread.IO)
.executeBy(taskExecutor)
}

}

View File

@ -77,6 +77,7 @@ internal class TaskExecutor @Inject constructor(private val coroutineDispatchers
TaskThread.IO -> coroutineDispatchers.io
TaskThread.CALLER -> EmptyCoroutineContext
TaskThread.CRYPTO -> coroutineDispatchers.crypto
TaskThread.SYNC -> coroutineDispatchers.sync
}



View File

@ -21,5 +21,6 @@ internal enum class TaskThread {
COMPUTATION,
IO,
CALLER,
CRYPTO
CRYPTO,
SYNC
}

View File

@ -22,5 +22,6 @@ internal data class MatrixCoroutineDispatchers(
val io: CoroutineDispatcher,
val computation: CoroutineDispatcher,
val main: CoroutineDispatcher,
val crypto: CoroutineDispatcher
val crypto: CoroutineDispatcher,
val sync: CoroutineDispatcher
)

View File

@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.timeline

import android.os.Handler
import android.os.Looper
import android.util.LongSparseArray
import android.view.View
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListUpdateCallback
@ -82,8 +83,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
fun onUrlLongClicked(url: String): Boolean
}

private val collapsedEventIds = linkedSetOf<String>()
private val mergeItemCollapseStates = HashMap<String, Boolean>()
private val collapsedEventIds = linkedSetOf<Long>()
private val mergeItemCollapseStates = HashMap<Long,Boolean>()
private val modelCache = arrayListOf<CacheItemData?>()

private var currentSnapshot: List<TimelineEvent> = emptyList()
@ -298,7 +299,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
} else {
collapsedEventIds.removeAll(mergedEventIds)
}
val mergeId = mergedEventIds.joinToString(separator = "_") { it }
val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() }
MergedHeaderItem(isCollapsed, mergeId, mergedData, avatarRenderer) {
mergeItemCollapseStates[event.localId] = it
requestModelBuild()
@ -329,7 +330,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
}

private data class CacheItemData(
val localId: String,
val localId: Long,
val eventId: String?,
val eventModel: EpoxyModel<*>? = null,
val mergedHeaderModel: MergedHeaderItem? = null,

View File

@ -78,7 +78,7 @@ data class MergedHeaderItem(private val isCollapsed: Boolean,
}

data class Data(
val eventId: String,
val eventId: Long,
val userId: String,
val memberName: String,
val avatarUrl: String?