Merge branch 'feature/Perf' into develop

This commit is contained in:
ganfra 2019-07-02 23:07:16 +02:00
commit bc2d321a84
30 changed files with 248 additions and 210 deletions

View File

@ -35,11 +35,11 @@ android {
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android")
implementation 'androidx.appcompat:appcompat:1.1.0-alpha01' implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'


testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
} }

View File

@ -24,8 +24,8 @@ import io.reactivex.Observable


class RxRoom(private val room: Room) { class RxRoom(private val room: Room) {


fun liveRoomSummary(): Observable<RoomSummary> { fun liveRoomSummary(fetchLastEvent: Boolean): Observable<RoomSummary> {
return room.liveRoomSummary.asObservable() return room.liveRoomSummary(fetchLastEvent).asObservable()
} }


fun liveRoomMemberIds(): Observable<List<String>> { fun liveRoomMemberIds(): Observable<List<String>> {

View File

@ -25,8 +25,8 @@ import io.reactivex.Observable


class RxSession(private val session: Session) { class RxSession(private val session: Session) {


fun liveRoomSummaries(): Observable<List<RoomSummary>> { fun liveRoomSummaries(fetchLastEvents: Boolean): Observable<List<RoomSummary>> {
return session.liveRoomSummaries().asObservable() return session.liveRoomSummaries(fetchLastEvents).asObservable()
} }


fun liveGroupSummaries(): Observable<List<GroupSummary>> { fun liveGroupSummaries(): Observable<List<GroupSummary>> {

View File

@ -86,7 +86,7 @@ static def gitRevisionDate() {
dependencies { dependencies {


def arrow_version = "0.8.0" def arrow_version = "0.8.0"
def support_version = '1.1.0-alpha03' def support_version = '1.1.0-beta01'
def moshi_version = '1.8.0' def moshi_version = '1.8.0'
def lifecycle_version = '2.0.0' def lifecycle_version = '2.0.0'
def coroutines_version = "1.0.1" def coroutines_version = "1.0.1"
@ -98,8 +98,8 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"


implementation "androidx.appcompat:appcompat:$support_version" implementation "androidx.appcompat:appcompat:1.1.0-beta01"
implementation "androidx.recyclerview:recyclerview:$support_version" implementation "androidx.recyclerview:recyclerview:1.1.0-alpha06"


implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
@ -107,7 +107,7 @@ dependencies {
// Network // Network
implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0' implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
implementation 'com.squareup.okhttp3:okhttp:3.11.0' implementation 'com.squareup.okhttp3:okhttp:3.14.1'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
implementation 'com.novoda:merlin:1.1.6' implementation 'com.novoda:merlin:1.1.6'
implementation "com.squareup.moshi:moshi-adapters:$moshi_version" implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
@ -120,7 +120,7 @@ dependencies {
kapt 'dk.ilios:realmfieldnameshelper:1.1.1' kapt 'dk.ilios:realmfieldnameshelper:1.1.1'


// Work // Work
implementation "androidx.work:work-runtime-ktx:2.1.0-beta01" implementation "androidx.work:work-runtime-ktx:2.1.0-rc01"


// FP // FP
implementation "io.arrow-kt:arrow-core:$arrow_version" implementation "io.arrow-kt:arrow-core:$arrow_version"
@ -155,11 +155,11 @@ dependencies {
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"


androidTestImplementation "org.koin:koin-test:$koin_version" androidTestImplementation "org.koin:koin-test:$koin_version"
androidTestImplementation 'androidx.test:core:1.1.0' androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.1.1' androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.0' androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44' androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
androidTestImplementation "io.mockk:mockk-android:1.8.13.kotlin13" androidTestImplementation "io.mockk:mockk-android:1.8.13.kotlin13"
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version" androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"

View File

@ -47,8 +47,8 @@ interface Room :
* A live [RoomSummary] associated with the room * A live [RoomSummary] associated with the room
* You can observe this summary to get dynamic data from this room. * You can observe this summary to get dynamic data from this room.
*/ */
val liveRoomSummary: LiveData<RoomSummary> fun liveRoomSummary(fetchLastEvent: Boolean = false): LiveData<RoomSummary>


val roomSummary: RoomSummary? fun roomSummary(fetchLastEvent: Boolean = false): RoomSummary?


} }

View File

@ -43,6 +43,6 @@ interface RoomService {
* Get a live list of room summaries. This list is refreshed as soon as the data changes. * Get a live list of room summaries. This list is refreshed as soon as the data changes.
* @return the [LiveData] of [RoomSummary] * @return the [LiveData] of [RoomSummary]
*/ */
fun liveRoomSummaries(): LiveData<List<RoomSummary>> fun liveRoomSummaries(fetchLastEvents: Boolean = true): LiveData<List<RoomSummary>>


} }

View File

@ -25,6 +25,7 @@ import android.text.TextUtils
import arrow.core.Try import arrow.core.Try
import com.squareup.moshi.Types import com.squareup.moshi.Types
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
@ -98,7 +99,7 @@ internal class CryptoManager @Inject constructor(
private val olmManager: OlmManager, private val olmManager: OlmManager,
// The credentials, // The credentials,
private val credentials: Credentials, private val credentials: Credentials,
private val myDeviceInfoHolder: MyDeviceInfoHolder, private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>,
// the crypto store // the crypto store
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
// Olm device // Olm device
@ -190,7 +191,7 @@ internal class CryptoManager @Inject constructor(
} }


override fun getMyDevice(): MXDeviceInfo { override fun getMyDevice(): MXDeviceInfo {
return myDeviceInfoHolder.myDevice return myDeviceInfoHolder.get().myDevice
} }


override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) { override fun getDevicesList(callback: MatrixCallback<DevicesListResponse>) {

View File

@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto.verification


import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.CancelCode
@ -55,7 +56,7 @@ import kotlin.collections.HashMap
@SessionScope @SessionScope
internal class DefaultSasVerificationService @Inject constructor(private val credentials: Credentials, internal class DefaultSasVerificationService @Inject constructor(private val credentials: Credentials,
private val cryptoStore: IMXCryptoStore, private val cryptoStore: IMXCryptoStore,
private val myDeviceInfoHolder: MyDeviceInfoHolder, private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>,
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
private val setDeviceVerificationAction: SetDeviceVerificationAction, private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val sendToDeviceTask: SendToDeviceTask, private val sendToDeviceTask: SendToDeviceTask,
@ -197,7 +198,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
cryptoStore, cryptoStore,
sendToDeviceTask, sendToDeviceTask,
taskExecutor, taskExecutor,
myDeviceInfoHolder.myDevice.fingerprint()!!, myDeviceInfoHolder.get().myDevice.fingerprint()!!,
startReq.transactionID!!, startReq.transactionID!!,
otherUserId) otherUserId)
addTransaction(tx) addTransaction(tx)
@ -366,7 +367,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
cryptoStore, cryptoStore,
sendToDeviceTask, sendToDeviceTask,
taskExecutor, taskExecutor,
myDeviceInfoHolder.myDevice.fingerprint()!!, myDeviceInfoHolder.get().myDevice.fingerprint()!!,
txID, txID,
userId, userId,
deviceID) deviceID)

View File

@ -16,29 +16,24 @@


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


import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
import javax.inject.Inject import javax.inject.Inject


internal class RoomSummaryMapper @Inject constructor(private val timelineEventFactory: TimelineEventFactory) {


internal class RoomSummaryMapper @Inject constructor( fun map(roomSummaryEntity: RoomSummaryEntity, getLatestEvent: Boolean = false): RoomSummary {
private val timelineEventFactory: TimelineEventFactory,
private val monarchy: Monarchy) {

fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
val tags = roomSummaryEntity.tags.map { val tags = roomSummaryEntity.tags.map {
RoomTag(it.tagName, it.tagOrder) RoomTag(it.tagName, it.tagOrder)
} }
val latestEvent = roomSummaryEntity.latestEvent?.let { val latestEvent = if (getLatestEvent) {
var ev: TimelineEvent? = null roomSummaryEntity.latestEvent?.let {
monarchy.doWithRealm { realm -> timelineEventFactory.create(it, it.realm)
ev = timelineEventFactory.create(it, realm)
} }
ev } else {
null
} }
return RoomSummary( return RoomSummary(
roomId = roomSummaryEntity.roomId, roomId = roomSummaryEntity.roomId,

View File

@ -48,58 +48,7 @@ internal class DefaultPushRuleService @Inject constructor(


override fun fetchPushRules(scope: String) { override fun fetchPushRules(scope: String) {
pushRulesTask pushRulesTask
.configureWith(Unit) .configureWith(GetPushRulesTask.Params(scope))
.dispatchTo(object : MatrixCallback<GetPushRulesResponse> {
override fun onSuccess(data: GetPushRulesResponse) {
monarchy.runTransactionSync { realm ->
//clear existings?
//TODO
realm.where(PushRulesEntity::class.java)
.equalTo(PusherEntityFields.USER_ID, sessionParams.credentials.userId)
.findAll().deleteAllFromRealm()

val content = PushRulesEntity(sessionParams.credentials.userId, scope, "content")
data.global.content?.forEach { rule ->
PushRulesMapper.map(rule).also {
content.pushRules.add(it)
}
}
realm.insertOrUpdate(content)

val override = PushRulesEntity(sessionParams.credentials.userId, scope, "override")
data.global.override?.forEach { rule ->
PushRulesMapper.map(rule).also {
override.pushRules.add(it)
}
}
realm.insertOrUpdate(override)

val rooms = PushRulesEntity(sessionParams.credentials.userId, scope, "room")
data.global.room?.forEach { rule ->
PushRulesMapper.map(rule).also {
rooms.pushRules.add(it)
}
}
realm.insertOrUpdate(rooms)

val senders = PushRulesEntity(sessionParams.credentials.userId, scope, "sender")
data.global.sender?.forEach { rule ->
PushRulesMapper.map(rule).also {
senders.pushRules.add(it)
}
}
realm.insertOrUpdate(senders)

val underrides = PushRulesEntity(sessionParams.credentials.userId, scope, "underride")
data.global.underride?.forEach { rule ->
PushRulesMapper.map(rule).also {
underrides.pushRules.add(it)
}
}
realm.insertOrUpdate(underrides)
}
}
})
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }



View File

@ -54,22 +54,6 @@ internal class DefaultPusherService @Inject constructor(
override fun refreshPushers() { override fun refreshPushers() {
getPusherTask getPusherTask
.configureWith(Unit) .configureWith(Unit)
.dispatchTo(object : MatrixCallback<GetPushersResponse> {
override fun onSuccess(data: GetPushersResponse) {
monarchy.runTransactionSync { realm ->
//clear existings?
realm.where(PusherEntity::class.java)
.equalTo(PusherEntityFields.USER_ID, sessionParam.credentials.userId)
.findAll().deleteAllFromRealm()
data.pushers?.forEach { jsonPusher ->
jsonPusher.toEntity(sessionParam.credentials.userId).also {
it.state = PusherState.REGISTERED
realm.insertOrUpdate(it)
}
}
}
}
})
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }



View File

@ -16,19 +16,81 @@
package im.vector.matrix.android.internal.session.pushers package im.vector.matrix.android.internal.session.pushers


import arrow.core.Try import arrow.core.Try
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse
import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
import im.vector.matrix.android.internal.database.model.PushRulesEntity
import im.vector.matrix.android.internal.database.model.PusherEntityFields
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync
import javax.inject.Inject import javax.inject.Inject




internal interface GetPushRulesTask : Task<Unit, GetPushRulesResponse> internal interface GetPushRulesTask : Task<GetPushRulesTask.Params, Unit> {


internal class DefaultGetPushRulesTask @Inject constructor(private val pushRulesApi: PushRulesApi) : GetPushRulesTask { data class Params(val scope: String)


override suspend fun execute(params: Unit): Try<GetPushRulesResponse> { }
return executeRequest {

internal class DefaultGetPushRulesTask @Inject constructor(private val pushRulesApi: PushRulesApi,
private val monarchy: Monarchy,
private val sessionParams: SessionParams) : GetPushRulesTask {

override suspend fun execute(params: GetPushRulesTask.Params): Try<Unit> {
return executeRequest<GetPushRulesResponse> {
apiCall = pushRulesApi.getAllRules() apiCall = pushRulesApi.getAllRules()
}.flatMap { response ->
val scope = params.scope
return monarchy.tryTransactionSync { realm ->
//clear existings?
//TODO
realm.where(PushRulesEntity::class.java)
.equalTo(PusherEntityFields.USER_ID, sessionParams.credentials.userId)
.findAll().deleteAllFromRealm()

val content = PushRulesEntity(sessionParams.credentials.userId, scope, "content")
response.global.content?.forEach { rule ->
PushRulesMapper.map(rule).also {
content.pushRules.add(it)
}
}
realm.insertOrUpdate(content)

val override = PushRulesEntity(sessionParams.credentials.userId, scope, "override")
response.global.override?.forEach { rule ->
PushRulesMapper.map(rule).also {
override.pushRules.add(it)
}
}
realm.insertOrUpdate(override)

val rooms = PushRulesEntity(sessionParams.credentials.userId, scope, "room")
response.global.room?.forEach { rule ->
PushRulesMapper.map(rule).also {
rooms.pushRules.add(it)
}
}
realm.insertOrUpdate(rooms)

val senders = PushRulesEntity(sessionParams.credentials.userId, scope, "sender")
response.global.sender?.forEach { rule ->
PushRulesMapper.map(rule).also {
senders.pushRules.add(it)
}
}
realm.insertOrUpdate(senders)

val underrides = PushRulesEntity(sessionParams.credentials.userId, scope, "underride")
response.global.underride?.forEach { rule ->
PushRulesMapper.map(rule).also {
underrides.pushRules.add(it)
}
}
realm.insertOrUpdate(underrides)
}
} }
} }
} }

View File

@ -16,17 +16,39 @@
package im.vector.matrix.android.internal.session.pushers package im.vector.matrix.android.internal.session.pushers


import arrow.core.Try import arrow.core.Try
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.pushers.PusherState
import im.vector.matrix.android.internal.database.mapper.toEntity
import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.database.model.PusherEntityFields
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync
import javax.inject.Inject import javax.inject.Inject


internal interface GetPushersTask : Task<Unit, GetPushersResponse> internal interface GetPushersTask : Task<Unit, Unit>


internal class DefaultGetPusherTask @Inject constructor(private val pushersAPI: PushersAPI) : GetPushersTask { internal class DefaultGetPusherTask @Inject constructor(private val pushersAPI: PushersAPI,
private val monarchy: Monarchy,
private val sessionParams: SessionParams) : GetPushersTask {


override suspend fun execute(params: Unit): Try<GetPushersResponse> { override suspend fun execute(params: Unit): Try<Unit> {
return executeRequest { return executeRequest<GetPushersResponse> {
apiCall = pushersAPI.getPushers() apiCall = pushersAPI.getPushers()
}.flatMap { response ->
monarchy.tryTransactionSync { realm ->
//clear existings?
realm.where(PusherEntity::class.java)
.equalTo(PusherEntityFields.USER_ID, sessionParams.credentials.userId)
.findAll().deleteAllFromRealm()
response.pushers?.forEach { jsonPusher ->
jsonPusher.toEntity(sessionParams.credentials.userId).also {
it.state = PusherState.REGISTERED
realm.insertOrUpdate(it)
}
}
}
} }
} }
} }

View File

@ -34,6 +34,7 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.util.fetchCopied import im.vector.matrix.android.internal.util.fetchCopied
import im.vector.matrix.android.internal.util.fetchCopyMap
import javax.inject.Inject import javax.inject.Inject


internal class DefaultRoom @Inject constructor(override val roomId: String, internal class DefaultRoom @Inject constructor(override val roomId: String,
@ -47,19 +48,19 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
private val relationService: RelationService, private val relationService: RelationService,
private val roomMembersService: MembershipService private val roomMembersService: MembershipService
) : Room, ) : Room,
TimelineService by timelineService, TimelineService by timelineService,
SendService by sendService, SendService by sendService,
StateService by stateService, StateService by stateService,
ReadService by readService, ReadService by readService,
RelationService by relationService, RelationService by relationService,
MembershipService by roomMembersService { MembershipService by roomMembersService {


override val liveRoomSummary: LiveData<RoomSummary> by lazy { override fun liveRoomSummary(fetchLastEvent: Boolean): LiveData<RoomSummary> {
val liveRealmData = RealmLiveData<RoomSummaryEntity>(monarchy.realmConfiguration) { realm -> val liveRealmData = RealmLiveData<RoomSummaryEntity>(monarchy.realmConfiguration) { realm ->
RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
} }
Transformations.map(liveRealmData) { results -> return Transformations.map(liveRealmData) { results ->
val roomSummaries = results.map { roomSummaryMapper.map(it) } val roomSummaries = results.map { roomSummaryMapper.map(it, fetchLastEvent) }


if (roomSummaries.isEmpty()) { if (roomSummaries.isEmpty()) {
// Create a dummy RoomSummary to avoid Crash during Sign Out or clear cache // Create a dummy RoomSummary to avoid Crash during Sign Out or clear cache
@ -70,11 +71,12 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
} }
} }


override val roomSummary: RoomSummary? override fun roomSummary(fetchLastEvent: Boolean): RoomSummary? {
get() { return monarchy.fetchAllMappedSync(
var sum: RoomSummaryEntity? = monarchy.fetchCopied { RoomSummaryEntity.where(it, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME).findFirst() } { realm -> RoomSummaryEntity.where(realm).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) },
return sum?.let { roomSummaryMapper.map(it) } { roomSummaryMapper.map(it, fetchLastEvent) }
} ).firstOrNull()
}


override fun isEncrypted(): Boolean { override fun isEncrypted(): Boolean {
return cryptoService.isRoomEncrypted(roomId) return cryptoService.isRoomEncrypted(roomId)

View File

@ -52,10 +52,10 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
return roomFactory.create(roomId) return roomFactory.create(roomId)
} }


override fun liveRoomSummaries(): LiveData<List<RoomSummary>> { override fun liveRoomSummaries(fetchLastEvents: Boolean): LiveData<List<RoomSummary>> {
return monarchy.findAllMappedWithChanges( return monarchy.findAllMappedWithChanges(
{ realm -> RoomSummaryEntity.where(realm).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) }, { realm -> RoomSummaryEntity.where(realm).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) },
{ roomSummaryMapper.map(it) } { roomSummaryMapper.map(it, fetchLastEvents) }
) )
} }
} }

View File

@ -30,7 +30,12 @@ import im.vector.matrix.android.api.util.addTo
import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
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.model.RoomEntity
import im.vector.matrix.android.internal.database.query.findIncludingEvent import im.vector.matrix.android.internal.database.query.findIncludingEvent
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
@ -38,7 +43,13 @@ import im.vector.matrix.android.internal.database.query.whereInRoom
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.Debouncer import im.vector.matrix.android.internal.util.Debouncer
import io.realm.* import io.realm.OrderedCollectionChangeSet
import io.realm.OrderedRealmCollectionChangeListener
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.Sort
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -50,7 +61,6 @@ import kotlin.collections.HashMap
private const val INITIAL_LOAD_SIZE = 20 private const val INITIAL_LOAD_SIZE = 20
private const val MIN_FETCHING_COUNT = 30 private const val MIN_FETCHING_COUNT = 30
private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE
private const val THREAD_NAME = "TIMELINE_DB_THREAD"


internal class DefaultTimeline( internal class DefaultTimeline(
private val roomId: String, private val roomId: String,
@ -64,18 +74,22 @@ internal class DefaultTimeline(
private val allowedTypes: List<String>? private val allowedTypes: List<String>?
) : Timeline { ) : Timeline {


private companion object {
val BACKGROUND_HANDLER = Handler(
HandlerThread("TIMELINE_DB_THREAD").apply { start() }.looper
)
}

override var listener: Timeline.Listener? = null override var listener: Timeline.Listener? = null
set(value) { set(value) {
field = value field = value
backgroundHandler.get()?.post { BACKGROUND_HANDLER.post {
postSnapshot() postSnapshot()
} }
} }


private val isStarted = AtomicBoolean(false) private val isStarted = AtomicBoolean(false)
private val isReady = AtomicBoolean(false) private val isReady = AtomicBoolean(false)
private val backgroundHandlerThread = AtomicReference<HandlerThread>()
private val backgroundHandler = AtomicReference<Handler>()
private val mainHandler = Handler(Looper.getMainLooper()) private val mainHandler = Handler(Looper.getMainLooper())
private val backgroundRealm = AtomicReference<Realm>() private val backgroundRealm = AtomicReference<Realm>()
private val cancelableBag = CancelableBag() private val cancelableBag = CancelableBag()
@ -168,14 +182,14 @@ internal class DefaultTimeline(
override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
if (roomId == this@DefaultTimeline.roomId) { if (roomId == this@DefaultTimeline.roomId) {
Timber.v("New session id detected for this room") Timber.v("New session id detected for this room")
backgroundHandler.get()?.post { BACKGROUND_HANDLER.post {
val realm = backgroundRealm.get() val realm = backgroundRealm.get()
var hasChange = false var hasChange = false
builtEvents.forEachIndexed { index, timelineEvent -> builtEvents.forEachIndexed { index, timelineEvent ->
if (timelineEvent.isEncrypted()) { if (timelineEvent.isEncrypted()) {
val eventContent = timelineEvent.root.content.toModel<EncryptedEventContent>() val eventContent = timelineEvent.root.content.toModel<EncryptedEventContent>()
if (eventContent?.sessionId == sessionId if (eventContent?.sessionId == sessionId
&& (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) { && (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) {
//we need to rebuild this event //we need to rebuild this event
EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let { EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let {
builtEvents[index] = timelineEventFactory.create(it, realm) builtEvents[index] = timelineEventFactory.create(it, realm)
@ -194,7 +208,7 @@ internal class DefaultTimeline(
// Public methods ****************************************************************************** // Public methods ******************************************************************************


override fun paginate(direction: Timeline.Direction, count: Int) { override fun paginate(direction: Timeline.Direction, count: Int) {
backgroundHandler.get()?.post { BACKGROUND_HANDLER.post {
if (!canPaginate(direction)) { if (!canPaginate(direction)) {
return@post return@post
} }
@ -211,13 +225,8 @@ internal class DefaultTimeline(
override fun start() { override fun start() {
if (isStarted.compareAndSet(false, true)) { if (isStarted.compareAndSet(false, true)) {
Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId") Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId")
val handlerThread = HandlerThread(THREAD_NAME + hashCode())
handlerThread.start()
val handler = Handler(handlerThread.looper)
this.backgroundHandlerThread.set(handlerThread)
this.backgroundHandler.set(handler)
cryptoService.addNewSessionListener(newSessionListener) cryptoService.addNewSessionListener(newSessionListener)
handler.post { BACKGROUND_HANDLER.post {
val realm = Realm.getInstance(realmConfiguration) val realm = Realm.getInstance(realmConfiguration)
backgroundRealm.set(realm) backgroundRealm.set(realm)
clearUnlinkedEvents(realm) clearUnlinkedEvents(realm)
@ -246,14 +255,12 @@ internal class DefaultTimeline(
if (isStarted.compareAndSet(true, false)) { if (isStarted.compareAndSet(true, false)) {
cryptoService.removeSessionListener(newSessionListener) cryptoService.removeSessionListener(newSessionListener)
Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId") Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId")
backgroundHandler.get()?.post { BACKGROUND_HANDLER.post {
cancelableBag.cancel() cancelableBag.cancel()
liveEvents.removeAllChangeListeners() liveEvents.removeAllChangeListeners()
backgroundRealm.getAndSet(null).also { backgroundRealm.getAndSet(null).also {
it.close() it.close()
} }
backgroundHandler.set(null)
backgroundHandlerThread.getAndSet(null)?.quit()
} }
} }
} }
@ -387,9 +394,9 @@ internal class DefaultTimeline(
private fun executePaginationTask(direction: Timeline.Direction, limit: Int) { private fun executePaginationTask(direction: Timeline.Direction, limit: Int) {
val token = getTokenLive(direction) ?: return val token = getTokenLive(direction) ?: return
val params = PaginationTask.Params(roomId = roomId, val params = PaginationTask.Params(roomId = roomId,
from = token, from = token,
direction = direction.toPaginationDirection(), direction = direction.toPaginationDirection(),
limit = limit) limit = limit)


Timber.v("Should fetch $limit items $direction") Timber.v("Should fetch $limit items $direction")
paginationTask.configureWith(params) paginationTask.configureWith(params)
@ -400,7 +407,7 @@ internal class DefaultTimeline(
Timber.v("Success fetching $limit items $direction from pagination request") Timber.v("Success fetching $limit items $direction from pagination request")
} else { } else {
// Database won't be updated, so we force pagination request // Database won't be updated, so we force pagination request
backgroundHandler.get()?.post { BACKGROUND_HANDLER.post {
executePaginationTask(direction, limit) executePaginationTask(direction, limit)
} }
} }
@ -441,6 +448,7 @@ internal class DefaultTimeline(
if (count < 1) { if (count < 1) {
return 0 return 0
} }
val start = System.currentTimeMillis()
val offsetResults = getOffsetResults(startDisplayIndex, direction, count) val offsetResults = getOffsetResults(startDisplayIndex, direction, count)
if (offsetResults.isEmpty()) { if (offsetResults.isEmpty()) {
return 0 return 0
@ -459,7 +467,8 @@ internal class DefaultTimeline(
builtEventsIdMap.entries.filter { it.value >= position }.forEach { it.setValue(it.value + 1) } builtEventsIdMap.entries.filter { it.value >= position }.forEach { it.setValue(it.value + 1) }
builtEventsIdMap[eventEntity.eventId] = position builtEventsIdMap[eventEntity.eventId] = position
} }
Timber.v("Built ${offsetResults.size} items from db") val time = System.currentTimeMillis() - start
Timber.v("Built ${offsetResults.size} items from db in $time ms")
return offsetResults.size return offsetResults.size
} }



View File

@ -80,7 +80,7 @@ internal class SimpleTimelineEventFactory @Inject constructor(private val roomMe
val result = cryptoService.decryptEvent(event, UUID.randomUUID().toString()) val result = cryptoService.decryptEvent(event, UUID.randomUUID().toString())
event.setClearData(result) event.setClearData(result)
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.e(failure, "Encrypted event: decryption failed") Timber.e("Encrypted event: decryption failed")
if (failure is MXDecryptionException) { if (failure is MXDecryptionException) {
event.setCryptoError(failure.cryptoError) event.setCryptoError(failure.cryptoError)
} }

View File

@ -156,9 +156,10 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"


implementation 'androidx.appcompat:appcompat:1.1.0-alpha03' implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
//Do not use beta2 at the moment, as it breaks things
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1'
implementation 'androidx.core:core-ktx:1.0.1' implementation 'androidx.core:core-ktx:1.0.2'


implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1' implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1'
implementation "com.squareup.moshi:moshi-adapters:$moshi_version" implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
@ -185,7 +186,7 @@ dependencies {
implementation 'com.airbnb.android:mvrx:1.0.1' implementation 'com.airbnb.android:mvrx:1.0.1'


// Work // Work
implementation "androidx.work:work-runtime-ktx:2.1.0-beta01" implementation "androidx.work:work-runtime-ktx:2.1.0-rc01"


// Functional Programming // Functional Programming
implementation "io.arrow-kt:arrow-core:$arrow_version" implementation "io.arrow-kt:arrow-core:$arrow_version"
@ -195,7 +196,7 @@ dependencies {


// UI // UI
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation 'com.google.android.material:material:1.1.0-alpha04' implementation 'com.google.android.material:material:1.1.0-alpha07'
implementation 'me.gujun.android:span:1.7' implementation 'me.gujun.android:span:1.7'
implementation "ru.noties.markwon:core:$markwon_version" implementation "ru.noties.markwon:core:$markwon_version"
implementation "ru.noties.markwon:html:$markwon_version" implementation "ru.noties.markwon:html:$markwon_version"
@ -246,8 +247,8 @@ dependencies {


// TESTS // TESTS
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
} }


if (!getGradle().getStartParameter().getTaskRequests().toString().contains("Fdroid")) { if (!getGradle().getStartParameter().getTaskRequests().toString().contains("Fdroid")) {

View File

@ -33,6 +33,7 @@ import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.HomeNavigator import im.vector.riotx.features.home.HomeNavigator
import im.vector.riotx.features.home.HomeRoomListObservableStore import im.vector.riotx.features.home.HomeRoomListObservableStore
import im.vector.riotx.features.home.group.SelectedGroupStore import im.vector.riotx.features.home.group.SelectedGroupStore
import im.vector.riotx.features.html.EventHtmlRenderer
import im.vector.riotx.features.navigation.Navigator import im.vector.riotx.features.navigation.Navigator
import im.vector.riotx.features.notifications.NotifiableEventResolver import im.vector.riotx.features.notifications.NotifiableEventResolver
import im.vector.riotx.features.notifications.NotificationBroadcastReceiver import im.vector.riotx.features.notifications.NotificationBroadcastReceiver
@ -68,6 +69,8 @@ interface VectorComponent {


fun emojiCompatFontProvider(): EmojiCompatFontProvider fun emojiCompatFontProvider(): EmojiCompatFontProvider


fun eventHtmlRenderer(): EventHtmlRenderer

fun navigator(): Navigator fun navigator(): Navigator


fun homeNavigator(): HomeNavigator fun homeNavigator(): HomeNavigator

View File

@ -73,7 +73,7 @@ class HomeActivityViewModel @AssistedInject constructor(@Assisted initialState:
private fun observeRoomAndGroup() { private fun observeRoomAndGroup() {
Observable Observable
.combineLatest<List<RoomSummary>, Option<GroupSummary>, List<RoomSummary>>( .combineLatest<List<RoomSummary>, Option<GroupSummary>, List<RoomSummary>>(
session.rx().liveRoomSummaries().throttleLast(300, TimeUnit.MILLISECONDS), session.rx().liveRoomSummaries(fetchLastEvents = true).throttleLast(300, TimeUnit.MILLISECONDS),
selectedGroupStore.observe(), selectedGroupStore.observe(),
BiFunction { rooms, selectedGroupOption -> BiFunction { rooms, selectedGroupOption ->
val selectedGroup = selectedGroupOption.orNull() val selectedGroup = selectedGroupOption.orNull()

View File

@ -498,7 +498,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }


private fun observeRoomSummary() { private fun observeRoomSummary() {
room.rx().liveRoomSummary() room.rx().liveRoomSummary(false)
.execute { async -> .execute { async ->
copy( copy(
asyncRoomSummary = async, asyncRoomSummary = async,

View File

@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.timeline.action
import com.airbnb.mvrx.* import com.airbnb.mvrx.*
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import dagger.Lazy
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.EventType 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.events.model.toModel
@ -84,7 +85,7 @@ data class MessageActionState(
*/ */
class MessageActionsViewModel @AssistedInject constructor(@Assisted class MessageActionsViewModel @AssistedInject constructor(@Assisted
initialState: MessageActionState, initialState: MessageActionState,
private val eventHtmlRenderer: EventHtmlRenderer, private val eventHtmlRenderer: Lazy<EventHtmlRenderer>,
session: Session, session: Session,
private val noticeEventFormatter: NoticeEventFormatter private val noticeEventFormatter: NoticeEventFormatter
) : VectorViewModel<MessageActionState>(initialState) { ) : VectorViewModel<MessageActionState>(initialState) {
@ -121,7 +122,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
} }


fun resolveBody(state: MessageActionState): CharSequence? { fun resolveBody(state: MessageActionState): CharSequence? {
return state.messageBody(eventHtmlRenderer, noticeEventFormatter) return state.messageBody(eventHtmlRenderer.get(), noticeEventFormatter)
} }


} }

View File

@ -23,12 +23,20 @@ import android.text.style.ClickableSpan
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import android.text.style.RelativeSizeSpan import android.text.style.RelativeSizeSpan
import android.view.View import android.view.View
import dagger.Lazy
import im.vector.matrix.android.api.permalinks.MatrixLinkify import im.vector.matrix.android.api.permalinks.MatrixLinkify
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.send.SendState 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.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
@ -55,7 +63,7 @@ class MessageItemFactory @Inject constructor(
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
private val htmlRenderer: EventHtmlRenderer, private val htmlRenderer: Lazy<EventHtmlRenderer>,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val emojiCompatFontProvider: EmojiCompatFontProvider, private val emojiCompatFontProvider: EmojiCompatFontProvider,
private val imageContentRenderer: ImageContentRenderer, private val imageContentRenderer: ImageContentRenderer,
@ -79,9 +87,9 @@ class MessageItemFactory @Inject constructor(


val messageContent: MessageContent = val messageContent: MessageContent =
event.annotations?.editSummary?.aggregatedContent?.toModel() event.annotations?.editSummary?.aggregatedContent?.toModel()
?: event.root.getClearContent().toModel() ?: event.root.getClearContent().toModel()
?: //Malformed content, we should echo something on screen ?: //Malformed content, we should echo something on screen
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message)) return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))


if (messageContent.relatesTo?.type == RelationType.REPLACE) { if (messageContent.relatesTo?.type == RelationType.REPLACE) {
// ignore replace event, the targeted id is already edited // ignore replace event, the targeted id is already edited
@ -91,16 +99,16 @@ class MessageItemFactory @Inject constructor(
// val ev = all.toModel<Event>() // val ev = all.toModel<Event>()
return when (messageContent) { return when (messageContent) {
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, is MessageEmoteContent -> buildEmoteMessageItem(messageContent,
informationData, informationData,
event.annotations?.editSummary, event.annotations?.editSummary,
highlight, highlight,
callback) callback)
is MessageTextContent -> buildTextMessageItem(event.sendState, is MessageTextContent -> buildTextMessageItem(event.sendState,
messageContent, messageContent,
informationData, informationData,
event.annotations?.editSummary, event.annotations?.editSummary,
highlight, highlight,
callback callback
) )
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, highlight, callback) is MessageImageContent -> buildImageMessageItem(messageContent, informationData, highlight, callback)
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback) is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback)
@ -134,7 +142,7 @@ class MessageItemFactory @Inject constructor(
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }


@ -157,7 +165,7 @@ class MessageItemFactory @Inject constructor(
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
.clickListener( .clickListener(
DebouncedClickListener(View.OnClickListener { _ -> DebouncedClickListener(View.OnClickListener { _ ->
@ -210,7 +218,7 @@ class MessageItemFactory @Inject constructor(
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }


@ -254,7 +262,7 @@ class MessageItemFactory @Inject constructor(
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) } .clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) }
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }


@ -266,7 +274,7 @@ class MessageItemFactory @Inject constructor(
callback: TimelineEventController.Callback?): MessageTextItem? { callback: TimelineEventController.Callback?): MessageTextItem? {


val bodyToUse = messageContent.formattedBody?.let { val bodyToUse = messageContent.formattedBody?.let {
htmlRenderer.render(it.trim()) htmlRenderer.get().render(it.trim())
} ?: messageContent.body } ?: messageContent.body


val linkifiedBody = linkifyBody(bodyToUse, callback) val linkifiedBody = linkifyBody(bodyToUse, callback)
@ -294,7 +302,7 @@ class MessageItemFactory @Inject constructor(
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }


@ -326,9 +334,9 @@ class MessageItemFactory @Inject constructor(
//nop //nop
} }
}, },
editStart, editStart,
editEnd, editEnd,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE) Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
return spannable return spannable
} }


@ -364,7 +372,7 @@ class MessageItemFactory @Inject constructor(
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }


@ -400,7 +408,7 @@ class MessageItemFactory @Inject constructor(
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }



View File

@ -18,10 +18,9 @@ package im.vector.riotx.features.html


import android.content.Context import android.content.Context
import android.text.style.URLSpan import android.text.style.URLSpan
import androidx.appcompat.app.AppCompatActivity
import im.vector.matrix.android.api.permalinks.PermalinkData import im.vector.matrix.android.api.permalinks.PermalinkData
import im.vector.matrix.android.api.permalinks.PermalinkParser import im.vector.matrix.android.api.permalinks.PermalinkParser
import im.vector.matrix.android.api.session.Session import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.core.glide.GlideRequests import im.vector.riotx.core.glide.GlideRequests
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
@ -37,12 +36,14 @@ import ru.noties.markwon.html.TagHandler
import ru.noties.markwon.html.tag.* import ru.noties.markwon.html.tag.*
import java.util.Arrays.asList import java.util.Arrays.asList
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton


class EventHtmlRenderer @Inject constructor(context: AppCompatActivity, @Singleton
class EventHtmlRenderer @Inject constructor(context: Context,
val avatarRenderer: AvatarRenderer, val avatarRenderer: AvatarRenderer,
session: Session) { sessionHolder: ActiveSessionHolder) {
private val markwon = Markwon.builder(context) private val markwon = Markwon.builder(context)
.usePlugin(MatrixPlugin.create(GlideApp.with(context), context, avatarRenderer, session)) .usePlugin(MatrixPlugin.create(GlideApp.with(context), context, avatarRenderer, sessionHolder))
.build() .build()


fun render(text: String): CharSequence { fun render(text: String): CharSequence {
@ -54,7 +55,7 @@ class EventHtmlRenderer @Inject constructor(context: AppCompatActivity,
private class MatrixPlugin private constructor(private val glideRequests: GlideRequests, private class MatrixPlugin private constructor(private val glideRequests: GlideRequests,
private val context: Context, private val context: Context,
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
private val session: Session) : AbstractMarkwonPlugin() { private val session: ActiveSessionHolder) : AbstractMarkwonPlugin() {


override fun configureConfiguration(builder: MarkwonConfiguration.Builder) { override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.htmlParser(MarkwonHtmlParserImpl.create()) builder.htmlParser(MarkwonHtmlParserImpl.create())
@ -99,7 +100,7 @@ private class MatrixPlugin private constructor(private val glideRequests: GlideR
asList<String>("h1", "h2", "h3", "h4", "h5", "h6"), asList<String>("h1", "h2", "h3", "h4", "h5", "h6"),
HeadingHandler()) HeadingHandler())
.setHandler("mx-reply", .setHandler("mx-reply",
MxReplyTagHandler()) MxReplyTagHandler())


} }


@ -122,7 +123,7 @@ private class MatrixPlugin private constructor(private val glideRequests: GlideR


companion object { companion object {


fun create(glideRequests: GlideRequests, context: Context, avatarRenderer: AvatarRenderer, session: Session): MatrixPlugin { fun create(glideRequests: GlideRequests, context: Context, avatarRenderer: AvatarRenderer, session: ActiveSessionHolder): MatrixPlugin {
return MatrixPlugin(glideRequests, context, avatarRenderer, session) return MatrixPlugin(glideRequests, context, avatarRenderer, session)
} }
} }
@ -131,7 +132,7 @@ private class MatrixPlugin private constructor(private val glideRequests: GlideR
private class MxLinkHandler(private val glideRequests: GlideRequests, private class MxLinkHandler(private val glideRequests: GlideRequests,
private val context: Context, private val context: Context,
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
private val session: Session) : TagHandler() { private val sessionHolder: ActiveSessionHolder) : TagHandler() {


private val linkHandler = LinkHandler() private val linkHandler = LinkHandler()


@ -141,7 +142,7 @@ private class MxLinkHandler(private val glideRequests: GlideRequests,
val permalinkData = PermalinkParser.parse(link) val permalinkData = PermalinkParser.parse(link)
when (permalinkData) { when (permalinkData) {
is PermalinkData.UserLink -> { is PermalinkData.UserLink -> {
val user = session.getUser(permalinkData.userId) val user = sessionHolder.getSafeActiveSession()?.getUser(permalinkData.userId)
val span = PillImageSpan(glideRequests, avatarRenderer, context, permalinkData.userId, user) val span = PillImageSpan(glideRequests, avatarRenderer, context, permalinkData.userId, user)
SpannableBuilder.setSpans( SpannableBuilder.setSpans(
visitor.builder(), visitor.builder(),

View File

@ -117,7 +117,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
val body = event.annotations?.editSummary?.aggregatedContent?.toModel<MessageContent>()?.body val body = event.annotations?.editSummary?.aggregatedContent?.toModel<MessageContent>()?.body
?: event.root.getClearContent().toModel<MessageContent>()?.body ?: event.root.getClearContent().toModel<MessageContent>()?.body
?: stringProvider.getString(R.string.notification_unknown_new_event) ?: stringProvider.getString(R.string.notification_unknown_new_event)
val roomName = room.roomSummary?.displayName ?: "" val roomName = room.roomSummary()?.displayName ?: ""
val senderDisplayName = event.senderName ?: event.root.senderId val senderDisplayName = event.senderName ?: event.root.senderId


val notifiableEvent = NotifiableMessageEvent( val notifiableEvent = NotifiableMessageEvent(
@ -129,7 +129,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
body = body, body = body,
roomId = event.root.roomId!!, roomId = event.root.roomId!!,
roomName = roomName, roomName = roomName,
roomIsDirect = room.roomSummary?.isDirect ?: false) roomIsDirect = room.roomSummary()?.isDirect ?: false)


notifiableEvent.matrixID = session.sessionParams.credentials.userId notifiableEvent.matrixID = session.sessionParams.credentials.userId
notifiableEvent.soundName = null notifiableEvent.soundName = null
@ -137,7 +137,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
// Get the avatars URL // Get the avatars URL
// TODO They will be not displayed the first time (known limitation) // TODO They will be not displayed the first time (known limitation)
notifiableEvent.roomAvatarPath = session.contentUrlResolver() notifiableEvent.roomAvatarPath = session.contentUrlResolver()
.resolveThumbnail(room.roomSummary?.avatarUrl, .resolveThumbnail(room.roomSummary()?.avatarUrl,
250, 250,
250, 250,
ContentUrlResolver.ThumbnailMethod.SCALE) ContentUrlResolver.ThumbnailMethod.SCALE)

View File

@ -100,8 +100,8 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
session.sessionParams.credentials.userId, session.sessionParams.credentials.userId,
message, message,
room.roomId, room.roomId,
room.roomSummary?.displayName ?: room.roomId, room.roomSummary()?.displayName ?: room.roomId,
room.roomSummary?.isDirect == true room.roomSummary()?.isDirect == true
) )
notifiableMessageEvent.outGoingMessage = true notifiableMessageEvent.outGoingMessage = true



View File

@ -86,7 +86,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
private fun observeJoinedRooms() { private fun observeJoinedRooms() {
session session
.rx() .rx()
.liveRoomSummaries() .liveRoomSummaries(fetchLastEvents = false)
.subscribe { list -> .subscribe { list ->
val joinedRoomIds = list val joinedRoomIds = list
// Keep only joined room // Keep only joined room

View File

@ -54,7 +54,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: R
private fun observeJoinedRooms() { private fun observeJoinedRooms() {
session session
.rx() .rx()
.liveRoomSummaries() .liveRoomSummaries(fetchLastEvents = false)
.subscribe { list -> .subscribe { list ->
withState { state -> withState { state ->
val isRoomJoined = list val isRoomJoined = list

View File

@ -23,6 +23,7 @@ import android.preference.PreferenceManager
import android.text.TextUtils import android.text.TextUtils
import android.util.Pair import android.util.Pair
import androidx.core.content.edit import androidx.core.content.edit
import kotlinx.coroutines.Dispatchers
import im.vector.riotx.R import im.vector.riotx.R
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -75,7 +76,7 @@ object VectorLocale {
} }


// init the known locales in background, using kotlin coroutines // init the known locales in background, using kotlin coroutines
GlobalScope.launch { GlobalScope.launch(Dispatchers.IO) {
initApplicationLocales(context) initApplicationLocales(context)
} }
} }

View File

@ -86,9 +86,7 @@ class VectorSettingsActivity : VectorBaseActivity(),
try { try {
pref?.fragment?.let { pref?.fragment?.let {
oFragment = supportFragmentManager.fragmentFactory oFragment = supportFragmentManager.fragmentFactory
.instantiate( .instantiate(classLoader, it)
classLoader,
it, pref.extras)
} }
} catch (e: Throwable) { } catch (e: Throwable) {
showSnackbar(getString(R.string.not_implemented)) showSnackbar(getString(R.string.not_implemented))