From 3519ad7c8d7d5b2089d9ce03ace162a0f115c2eb Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 23 May 2019 19:12:06 +0200 Subject: [PATCH] Crypto : WIP --- build.gradle | 2 +- .../SingleThreadCoroutineDispatcher.kt | 2 +- .../room/model/RoomHistoryVisibility.kt | 2 +- .../android/internal/crypto/CryptoManager.kt | 84 +++++++------------ .../internal/crypto/OneTimeKeysUploader.kt | 3 + .../crypto/live/EnableEncryptionWorker.kt | 17 +++- .../android/internal/di/MatrixModule.kt | 9 +- .../internal/session/DefaultSession.kt | 15 ++-- .../internal/session/sync/SyncModule.kt | 2 +- .../session/sync/SyncResponseHandler.kt | 7 +- .../android/internal/task/TaskExecutor.kt | 2 + .../android/internal/task/TaskThread.kt | 4 +- .../util/MatrixCoroutineDispatchers.kt | 4 +- .../timeline/TimelineEventController.kt | 1 + 14 files changed, 81 insertions(+), 73 deletions(-) diff --git a/build.gradle b/build.gradle index 8c9840e921..14d07f074d 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:3.4.0' + classpath 'com.android.tools.build:gradle:3.4.1' classpath 'com.google.gms:google-services:4.2.0' classpath "com.airbnb.okreplay:gradle-plugin:1.4.0" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/SingleThreadCoroutineDispatcher.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/SingleThreadCoroutineDispatcher.kt index 97f458c1f7..69803c5dc1 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/SingleThreadCoroutineDispatcher.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/SingleThreadCoroutineDispatcher.kt @@ -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) \ No newline at end of file +internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomHistoryVisibility.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomHistoryVisibility.kt index 24fc5bbe96..9260b0112c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomHistoryVisibility.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomHistoryVisibility.kt @@ -22,5 +22,5 @@ enum class RoomHistoryVisibility { @Json(name = "shared") SHARED, @Json(name = "invited") INVITED, @Json(name = "joined") JOINED, - @Json(name = "word_readable") WORLD_READABLE + @Json(name = "world_readable") WORLD_READABLE } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt index d7f9777c73..b8ff9eaf34 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt @@ -35,8 +35,10 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.Room +import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent +import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import im.vector.matrix.android.internal.crypto.actions.MegolmSessionDataImporter import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction @@ -44,19 +46,29 @@ import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFactory import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup -import im.vector.matrix.android.internal.crypto.model.* +import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult +import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo +import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult +import im.vector.matrix.android.internal.crypto.model.MXOlmSessionResult +import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore -import im.vector.matrix.android.internal.crypto.tasks.* +import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask +import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask +import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask +import im.vector.matrix.android.internal.crypto.tasks.GetKeyChangesTask +import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask +import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask +import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService import im.vector.matrix.android.internal.di.MoshiProvider -import im.vector.matrix.android.internal.session.room.members.RoomMembers import im.vector.matrix.android.internal.session.sync.model.SyncResponse 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 org.matrix.olm.OlmManager import timber.log.Timber @@ -230,15 +242,8 @@ internal class CryptoManager( * devices. * * @param isInitialSync true if it starts from an initial sync - * @param aCallback the asynchronous callback */ - fun start(isInitialSync: Boolean, aCallback: MatrixCallback?) { - synchronized(mInitializationCallbacks) { - if (null != aCallback && mInitializationCallbacks.indexOf(aCallback) < 0) { - mInitializationCallbacks.add(aCallback) - } - } - + fun start(isInitialSync: Boolean) { if (mIsStarting) { return } @@ -260,11 +265,11 @@ internal class CryptoManager( uploadDeviceKeys(object : MatrixCallback { private fun onError() { Handler().postDelayed({ - if (!isStarted()) { - mIsStarting = false - start(isInitialSync, null) - } - }, 1000) + if (!isStarted()) { + mIsStarting = false + start(isInitialSync) + } + }, 1000) } override fun onSuccess(data: KeysUploadResponse) { @@ -290,13 +295,6 @@ internal class CryptoManager( mKeysBackup.checkAndStartKeysBackup() - synchronized(mInitializationCallbacks) { - for (callback in mInitializationCallbacks) { - callback.onSuccess(Unit) - } - mInitializationCallbacks.clear() - } - if (isInitialSync) { // refresh the devices list for each known room members deviceListManager.invalidateAllDeviceLists() @@ -605,17 +603,7 @@ internal class CryptoManager( if (!isStarted()) { Timber.v("## encryptEventContent() : wait after e2e init") - start(false, object : MatrixCallback { - override fun onSuccess(data: Unit) { - encryptEventContent(eventContent, eventType, room, callback) - } - - override fun onFailure(failure: Throwable) { - Timber.e(failure, "## encryptEventContent() : onNetworkError while waiting to start e2e") - - callback.onFailure(failure) - } - }) + start(false) return } @@ -669,11 +657,11 @@ internal class CryptoManager( } else { val algorithm = room.encryptionAlgorithm() val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, - algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON) + algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON) Timber.e("## encryptEventContent() : $reason") callback.onFailure(Failure.CryptoError(MXCryptoError(MXCryptoError.UNABLE_TO_ENCRYPT_ERROR_CODE, - MXCryptoError.UNABLE_TO_ENCRYPT, reason))) + MXCryptoError.UNABLE_TO_ENCRYPT, reason))) } } @@ -703,7 +691,7 @@ internal class CryptoManager( val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, eventContent["algorithm"] as String) Timber.e("## decryptEvent() : $reason") exceptions.add(MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, - MXCryptoError.UNABLE_TO_DECRYPT, reason))) + MXCryptoError.UNABLE_TO_DECRYPT, reason))) } else { try { result = alg.decryptEvent(event, timeline) @@ -796,23 +784,15 @@ internal class CryptoManager( // No encrypting in this room return } - - val userId = event.stateKey!! - - /* FIXME - val room = mRoomService.getRoom(roomId) - - val roomMember = room?.getRoomMember(userId) - - if (null != roomMember) { - val membership = roomMember.membership - + event.stateKey?.let { userId -> + val roomMember: RoomMember? = event.content.toModel() + val membership = roomMember?.membership if (membership == Membership.JOIN) { // make sure we are tracking the deviceList for this user. deviceListManager.startTrackingDeviceList(Arrays.asList(userId)) } else if (membership == Membership.INVITE - && shouldEncryptForInvitedMembers(roomId) - && mCryptoConfig.mEnableEncryptionForInvitedMembers) { + && shouldEncryptForInvitedMembers(roomId) + && mCryptoConfig.mEnableEncryptionForInvitedMembers) { // track the deviceList for this invited user. // Caution: there's a big edge case here in that federated servers do not // know what other servers are in the room at the time they've been invited. @@ -821,7 +801,6 @@ internal class CryptoManager( deviceListManager.startTrackingDeviceList(Arrays.asList(userId)) } } - */ } private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) { @@ -851,6 +830,7 @@ internal class CryptoManager( // same one as used in login. mUploadKeysTask .configureWith(UploadKeysTask.Params(getMyDevice().toDeviceKeys(), null, getMyDevice().deviceId)) + .executeOn(TaskThread.ENCRYPTION) .dispatchTo(callback) .executeBy(mTaskExecutor) } @@ -980,7 +960,7 @@ internal class CryptoManager( // trigger an an unknown devices exception callback.onFailure( Failure.CryptoError(MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE, - MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices))) + MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices))) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OneTimeKeysUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OneTimeKeysUploader.kt index 2ff667cfd9..02754cddbb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OneTimeKeysUploader.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OneTimeKeysUploader.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask import im.vector.matrix.android.internal.di.MoshiProvider 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 org.matrix.olm.OlmAccount import timber.log.Timber @@ -95,6 +96,7 @@ internal class OneTimeKeysUploader( // ask the server how many keys we have mUploadKeysTask .configureWith(UploadKeysTask.Params(null, null, mCredentials.deviceId!!)) + .executeOn(TaskThread.ENCRYPTION) .dispatchTo(object : MatrixCallback { override fun onSuccess(data: KeysUploadResponse) { @@ -192,6 +194,7 @@ internal class OneTimeKeysUploader( // same one as used in login. mUploadKeysTask .configureWith(UploadKeysTask.Params(null, oneTimeJson, mCredentials.deviceId!!)) + .executeOn(TaskThread.ENCRYPTION) .dispatchTo(object : MatrixCallback { override fun onSuccess(data: KeysUploadResponse) { mLastPublishedOneTimeKeys = oneTimeKeys diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/live/EnableEncryptionWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/live/EnableEncryptionWorker.kt index 8cbab2f59f..baa8196191 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/live/EnableEncryptionWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/live/EnableEncryptionWorker.kt @@ -21,6 +21,7 @@ import androidx.work.Worker import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.internal.crypto.CryptoManager import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity @@ -50,8 +51,8 @@ internal class EnableEncryptionWorker(context: Context, override fun doWork(): Result { - val params = WorkerParamsFactory.fromData(inputData) - ?: return Result.failure() + val params = WorkerParamsFactory.fromData(inputData) + ?: return Result.failure() val events = monarchy.fetchAllMappedSync( @@ -62,9 +63,17 @@ internal class EnableEncryptionWorker(context: Context, events.forEach { val roomId = it.roomId!! + val callback = object : MatrixCallback { + override fun onSuccess(data: Boolean) { + super.onSuccess(data) + + } + } + loadRoomMembersTask .configureWith(LoadRoomMembersTask.Params(roomId)) - .executeOn(TaskThread.CALLER) + .executeOn(TaskThread.ENCRYPTION) + .dispatchTo(callback) .executeBy(taskExecutor) var userIds: List = emptyList() @@ -72,7 +81,7 @@ internal class EnableEncryptionWorker(context: Context, monarchy.doWithRealm { realm -> // Check whether the event content must be encrypted for the invited members. val encryptForInvitedMembers = cryptoManager.isEncryptionEnabledForInvitedUser() - && cryptoManager.shouldEncryptForInvitedMembers(roomId) + && cryptoManager.shouldEncryptForInvitedMembers(roomId) userIds = if (encryptForInvitedMembers) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt index f750c98176..3db692c8af 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt @@ -21,7 +21,9 @@ import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.util.BackgroundDetectionObserver import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asCoroutineDispatcher import org.koin.dsl.module.module +import java.util.concurrent.Executors class MatrixModule(private val context: Context) { @@ -33,7 +35,12 @@ class MatrixModule(private val context: Context) { } single { - MatrixCoroutineDispatchers(io = Dispatchers.IO, computation = Dispatchers.IO, main = Dispatchers.Main) + MatrixCoroutineDispatchers(io = Dispatchers.IO, + computation = Dispatchers.IO, + main = Dispatchers.Main, + encryption = Executors.newSingleThreadExecutor().asCoroutineDispatcher(), + decryption = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + ) } single { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index d3ba342c5e..94fa446032 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -103,18 +103,17 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi val contentModule = ContentModule().definition val cryptoModule = CryptoModule().definition MatrixKoinHolder.instance.loadModules(listOf(sessionModule, - syncModule, - roomModule, - groupModule, - userModule, - signOutModule, - contentModule, - cryptoModule)) + syncModule, + roomModule, + groupModule, + userModule, + signOutModule, + contentModule, + cryptoModule)) scope = getKoin().getOrCreateScope(SCOPE) if (!monarchy.isMonarchyThreadOpen) { monarchy.openManually() } - cryptoService.start(false, null) liveEntityUpdaters.forEach { it.start() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt index 9ab7a259f8..5fd7f6108e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt @@ -56,7 +56,7 @@ internal class SyncModule { } scope(DefaultSession.SCOPE) { - SyncResponseHandler(get(), get(), get(), get()) + SyncResponseHandler(get(), get(), get(), get(), get()) } scope(DefaultSession.SCOPE) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt index 011e2ddb0b..ba5e6ab661 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.session.sync import arrow.core.Try +import im.vector.matrix.android.internal.crypto.CryptoManager import im.vector.matrix.android.internal.session.sync.model.SyncResponse import timber.log.Timber import kotlin.system.measureTimeMillis @@ -24,7 +25,8 @@ import kotlin.system.measureTimeMillis internal class SyncResponseHandler(private val roomSyncHandler: RoomSyncHandler, private val userAccountDataSyncHandler: UserAccountDataSyncHandler, private val groupSyncHandler: GroupSyncHandler, - private val cryptoSyncHandler: CryptoSyncHandler) { + private val cryptoSyncHandler: CryptoSyncHandler, + private val cryptoManager: CryptoManager) { fun handleResponse(syncResponse: SyncResponse, fromToken: String?, isCatchingUp: Boolean): Try { return Try { @@ -44,9 +46,10 @@ internal class SyncResponseHandler(private val roomSyncHandler: RoomSyncHandler, if (syncResponse.accountData != null) { userAccountDataSyncHandler.handle(syncResponse.accountData) } - cryptoSyncHandler.onSyncCompleted(syncResponse, fromToken, isCatchingUp) } + val isInitialSync = fromToken == null + cryptoManager.start(isInitialSync) Timber.v("Finish handling sync in $measure ms") syncResponse } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt index a685581764..95fb118a6b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt @@ -68,6 +68,8 @@ internal class TaskExecutor(private val coroutineDispatchers: MatrixCoroutineDis TaskThread.COMPUTATION -> coroutineDispatchers.computation TaskThread.IO -> coroutineDispatchers.io TaskThread.CALLER -> EmptyCoroutineContext + TaskThread.ENCRYPTION -> coroutineDispatchers.encryption + TaskThread.DECRYPTION -> coroutineDispatchers.decryption } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskThread.kt index 1f74f4ae00..b7cc212a46 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskThread.kt @@ -20,5 +20,7 @@ internal enum class TaskThread { MAIN, COMPUTATION, IO, - CALLER + CALLER, + ENCRYPTION, + DECRYPTION } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/MatrixCoroutineDispatchers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/MatrixCoroutineDispatchers.kt index f198864f27..9628232e49 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/MatrixCoroutineDispatchers.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/MatrixCoroutineDispatchers.kt @@ -21,5 +21,7 @@ import kotlinx.coroutines.CoroutineDispatcher internal data class MatrixCoroutineDispatchers( val io: CoroutineDispatcher, val computation: CoroutineDispatcher, - val main: CoroutineDispatcher + val main: CoroutineDispatcher, + val decryption: CoroutineDispatcher, + val encryption: CoroutineDispatcher ) \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index c40511e8d6..bf2066234c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotredesign.core.epoxy.LoadingItemModel_ +import im.vector.riotredesign.core.epoxy.loadingItem import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.features.home.room.detail.timeline.factory.TimelineItemFactory import im.vector.riotredesign.features.home.room.detail.timeline.helper.*