Dagger: prepare for multi session [WIP]

This commit is contained in:
ganfra 2019-06-26 15:22:44 +02:00
parent 47968c9447
commit 6e7adaec59
74 changed files with 727 additions and 358 deletions

View File

@ -53,7 +53,6 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver @Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
@Inject internal lateinit var olmManager: OlmManager @Inject internal lateinit var olmManager: OlmManager
@Inject internal lateinit var sessionManager: SessionManager @Inject internal lateinit var sessionManager: SessionManager
var currentSession: Session? = null


init { init {
Monarchy.init(context) Monarchy.init(context)
@ -63,12 +62,6 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
} }
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver) ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
userAgentHolder.setApplicationFlavor(matrixConfiguration.applicationFlavor) userAgentHolder.setApplicationFlavor(matrixConfiguration.applicationFlavor)
authenticator.getLastActiveSession()?.also {
currentSession = it
it.open()
it.setFilter(FilterService.FilterPreset.RiotFilter)
it.startSync()
}
} }


fun getUserAgent() = userAgentHolder.userAgent fun getUserAgent() = userAgentHolder.userAgent

View File

@ -18,6 +18,7 @@ package im.vector.matrix.android.api.auth


import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable


@ -40,14 +41,24 @@ interface Authenticator {
* Check if there is an active [Session]. * Check if there is an active [Session].
* @return true if there is at least one active session. * @return true if there is at least one active session.
*/ */
fun hasActiveSessions(): Boolean fun hasAuthenticatedSessions(): Boolean


//TODO remove this method. Shouldn't be managed like that. //TODO remove this method. Shouldn't be managed like that.
/** /**
* Get the last active [Session], if there is an active session. * Get the last active [Session], if there is an active session.
* @return the lastActive session if any, or null * @return the lastActive session if any, or null
*/ */
fun getLastActiveSession(): Session? fun getLastAuthenticatedSession(): Session?

/**
* Get an authenticated session. You should at least call authenticate one time before.
* If you logout, this session will no longer be valid.
*
* @param sessionParams the sessionParams to open with.
* @return the associated session if any, or null
*/
fun getSession(sessionParams: SessionParams): Session?





} }

View File

@ -20,6 +20,7 @@ package im.vector.matrix.android.internal


import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.di.MatrixComponent import im.vector.matrix.android.internal.di.MatrixComponent
import im.vector.matrix.android.internal.di.MatrixScope import im.vector.matrix.android.internal.di.MatrixScope
import im.vector.matrix.android.internal.session.DaggerSessionComponent import im.vector.matrix.android.internal.session.DaggerSessionComponent
@ -27,27 +28,18 @@ import im.vector.matrix.android.internal.session.SessionComponent
import javax.inject.Inject import javax.inject.Inject


@MatrixScope @MatrixScope
internal class SessionManager @Inject constructor(private val matrixComponent: MatrixComponent) { internal class SessionManager @Inject constructor(private val matrixComponent: MatrixComponent,
private val sessionParamsStore: SessionParamsStore) {


private val sessionComponents = HashMap<String, SessionComponent>() private val sessionComponents = HashMap<String, SessionComponent>()


fun getSessionComponent(userId: String): SessionComponent? { fun getSessionComponent(userId: String): SessionComponent? {
return sessionComponents[userId] val sessionParams = sessionParamsStore.get(userId) ?: return null
return getOrCreateSessionComponent(sessionParams)
} }


fun createSession(sessionParams: SessionParams): Session { fun getOrCreateSession(sessionParams: SessionParams): Session {
val userId = sessionParams.credentials.userId return getOrCreateSessionComponent(sessionParams).session()
if (sessionComponents.containsKey(userId)) {
throw RuntimeException("You already have a session for the user $userId")
}
return DaggerSessionComponent
.factory()
.create(matrixComponent, sessionParams)
.also {
sessionComponents[userId] = it
}.let {
it.session()
}
} }


fun releaseSession(userId: String) { fun releaseSession(userId: String) {
@ -59,5 +51,17 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M
} }
} }


private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
val userId = sessionParams.credentials.userId
if (sessionComponents.containsKey(userId)) {
return sessionComponents[userId]!!
}
return DaggerSessionComponent
.factory()
.create(matrixComponent, sessionParams)
.also {
sessionComponents[sessionParams.credentials.userId] = it
}
}


} }

View File

@ -49,6 +49,8 @@ internal abstract class AuthModule {
} }
} }




@Binds @Binds
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore



View File

@ -28,32 +28,41 @@ import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.auth.data.ThreePidMedium import im.vector.matrix.android.internal.auth.data.ThreePidMedium
import im.vector.matrix.android.internal.di.MatrixScope import im.vector.matrix.android.internal.di.MatrixScope
import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import retrofit2.Retrofit import retrofit2.Retrofit
import javax.inject.Inject import javax.inject.Inject


internal class DefaultAuthenticator @Inject constructor(private val retrofitBuilder: Retrofit.Builder, internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
private val okHttpClient: OkHttpClient,
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionParamsStore: SessionParamsStore, private val sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager private val sessionManager: SessionManager
) : Authenticator { ) : Authenticator {


override fun hasActiveSessions(): Boolean { override fun hasAuthenticatedSessions(): Boolean {
return sessionParamsStore.get() != null return sessionParamsStore.getLast() != null
} }


override fun getLastActiveSession(): Session? { override fun getLastAuthenticatedSession(): Session? {
val sessionParams = sessionParamsStore.get() val sessionParams = sessionParamsStore.getLast()
return sessionParams?.let { return sessionParams?.let {
sessionManager.createSession(it) sessionManager.getOrCreateSession(it)
} }
} }


override fun getSession(sessionParams: SessionParams): Session? {
return sessionManager.getOrCreateSession(sessionParams)
}

override fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig, override fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig,
login: String, login: String,
password: String, password: String,
@ -84,13 +93,13 @@ internal class DefaultAuthenticator @Inject constructor(private val retrofitBuil
sessionParamsStore.save(sessionParams) sessionParamsStore.save(sessionParams)
sessionParams sessionParams
}.map { }.map {
sessionManager.createSession(it) sessionManager.getOrCreateSession(it)
} }


} }


private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
val retrofit = retrofitBuilder.baseUrl(homeServerConnectionConfig.homeServerUri.toString()).build() val retrofit = retrofitFactory.create(okHttpClient, homeServerConnectionConfig.homeServerUri.toString())
return retrofit.create(AuthAPI::class.java) return retrofit.create(AuthAPI::class.java)
} }



View File

@ -21,9 +21,15 @@ import im.vector.matrix.android.api.auth.data.SessionParams


internal interface SessionParamsStore { internal interface SessionParamsStore {


fun get(): SessionParams? fun get(userId: String): SessionParams?

fun getLast(): SessionParams?

fun getAll(): List<SessionParams>


fun save(sessionParams: SessionParams): Try<SessionParams> fun save(sessionParams: SessionParams): Try<SessionParams>


fun delete(): Try<Unit> fun delete(userId: String): Try<Unit>

fun deleteAll(): Try<Unit>
} }

View File

@ -20,15 +20,48 @@ import arrow.core.Try
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.di.AuthDatabase import im.vector.matrix.android.internal.di.AuthDatabase
import im.vector.matrix.android.internal.di.MatrixScope
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import javax.inject.Inject import javax.inject.Inject


internal class RealmSessionParamsStore @Inject constructor(private val mapper: SessionParamsMapper, internal class RealmSessionParamsStore @Inject constructor(private val mapper: SessionParamsMapper,
@AuthDatabase private val realmConfiguration: RealmConfiguration @AuthDatabase
private val realmConfiguration: RealmConfiguration
) : SessionParamsStore { ) : SessionParamsStore {


override fun getLast(): SessionParams? {
val realm = Realm.getInstance(realmConfiguration)
val sessionParams = realm
.where(SessionParamsEntity::class.java)
.findAll()
.map { mapper.map(it) }
.lastOrNull()
realm.close()
return sessionParams
}

override fun get(userId: String): SessionParams? {
val realm = Realm.getInstance(realmConfiguration)
val sessionParams = realm
.where(SessionParamsEntity::class.java)
.equalTo(SessionParamsEntityFields.USER_ID, userId)
.findAll()
.map { mapper.map(it) }
.firstOrNull()
realm.close()
return sessionParams
}

override fun getAll(): List<SessionParams> {
val realm = Realm.getInstance(realmConfiguration)
val sessionParams = realm
.where(SessionParamsEntity::class.java)
.findAll()
.mapNotNull { mapper.map(it) }
realm.close()
return sessionParams
}

override fun save(sessionParams: SessionParams): Try<SessionParams> { override fun save(sessionParams: SessionParams): Try<SessionParams> {
return Try { return Try {
val entity = mapper.map(sessionParams) val entity = mapper.map(sessionParams)
@ -43,18 +76,20 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
} }
} }


override fun get(): SessionParams? { override fun delete(userId: String): Try<Unit> {
val realm = Realm.getInstance(realmConfiguration) return Try {
val sessionParams = realm val realm = Realm.getInstance(realmConfiguration)
.where(SessionParamsEntity::class.java) realm.executeTransaction {
.findAll() it.where(SessionParamsEntity::class.java)
.map { mapper.map(it) } .equalTo(SessionParamsEntityFields.USER_ID, userId)
.lastOrNull() .findAll()
realm.close() .deleteAllFromRealm()
return sessionParams }
realm.close()
}
} }


override fun delete(): Try<Unit> { override fun deleteAll(): Try<Unit> {
return Try { return Try {
val realm = Realm.getInstance(realmConfiguration) val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction { realm.executeTransaction {

View File

@ -17,8 +17,10 @@
package im.vector.matrix.android.internal.auth.db package im.vector.matrix.android.internal.auth.db


import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey


internal open class SessionParamsEntity( internal open class SessionParamsEntity(
@PrimaryKey var userId: String = "",
var credentialsJson: String = "", var credentialsJson: String = "",
var homeServerConnectionConfigJson: String = "" var homeServerConnectionConfigJson: String = ""
) : RealmObject() ) : RealmObject()

View File

@ -49,7 +49,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
if (credentialsJson == null || homeServerConnectionConfigJson == null) { if (credentialsJson == null || homeServerConnectionConfigJson == null) {
return null return null
} }
return SessionParamsEntity(credentialsJson, homeServerConnectionConfigJson) return SessionParamsEntity(sessionParams.credentials.userId, credentialsJson, homeServerConnectionConfigJson)
} }





View File

@ -0,0 +1,29 @@
/*
*
* * Copyright 2019 New Vector Ltd
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/

package im.vector.matrix.android.internal.di

import javax.inject.Qualifier

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class Authenticated

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class Unauthenticated

View File

@ -23,9 +23,11 @@ import dagger.BindsInstance
import dagger.Component import dagger.Component
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.auth.Authenticator import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.AuthModule import im.vector.matrix.android.internal.auth.AuthModule
import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
@ -42,8 +44,7 @@ internal interface MatrixComponent {


fun moshi(): Moshi fun moshi(): Moshi


fun retrofitBuilder(): Retrofit.Builder @Unauthenticated

fun okHttpClient(): OkHttpClient fun okHttpClient(): OkHttpClient


fun authenticator(): Authenticator fun authenticator(): Authenticator
@ -62,6 +63,8 @@ internal interface MatrixComponent {


fun backgroundDetectionObserver(): BackgroundDetectionObserver fun backgroundDetectionObserver(): BackgroundDetectionObserver


fun sessionManager(): SessionManager

fun inject(matrix: Matrix) fun inject(matrix: Matrix)


@Component.Factory @Component.Factory

View File

@ -66,9 +66,9 @@ internal object NetworkModule {
@MatrixScope @MatrixScope
@Provides @Provides
@JvmStatic @JvmStatic
@Unauthenticated
fun providesOkHttpClient(stethoInterceptor: StethoInterceptor, fun providesOkHttpClient(stethoInterceptor: StethoInterceptor,
userAgentInterceptor: UserAgentInterceptor, userAgentInterceptor: UserAgentInterceptor,
accessTokenInterceptor: AccessTokenInterceptor,
httpLoggingInterceptor: HttpLoggingInterceptor, httpLoggingInterceptor: HttpLoggingInterceptor,
curlLoggingInterceptor: CurlLoggingInterceptor, curlLoggingInterceptor: CurlLoggingInterceptor,
okReplayInterceptor: OkReplayInterceptor): OkHttpClient { okReplayInterceptor: OkReplayInterceptor): OkHttpClient {
@ -78,7 +78,6 @@ internal object NetworkModule {
.writeTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS)
.addNetworkInterceptor(stethoInterceptor) .addNetworkInterceptor(stethoInterceptor)
.addInterceptor(userAgentInterceptor) .addInterceptor(userAgentInterceptor)
.addInterceptor(accessTokenInterceptor)
.addInterceptor(httpLoggingInterceptor) .addInterceptor(httpLoggingInterceptor)
.apply { .apply {
if (BuildConfig.LOG_PRIVATE_DATA) { if (BuildConfig.LOG_PRIVATE_DATA) {
@ -94,15 +93,4 @@ internal object NetworkModule {
fun providesMoshi(): Moshi { fun providesMoshi(): Moshi {
return MoshiProvider.providesMoshi() return MoshiProvider.providesMoshi()
} }

@Provides
@JvmStatic
fun providesRetrofitBuilder(okHttpClient: OkHttpClient,
moshi: Moshi): Retrofit.Builder {
return Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(UnitConverterFactory)
.addConverterFactory(MoshiConverterFactory.create(moshi))
}

} }

View File

@ -16,22 +16,19 @@


package im.vector.matrix.android.internal.network package im.vector.matrix.android.internal.network


import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.di.MatrixScope import im.vector.matrix.android.internal.session.SessionScope
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
import javax.inject.Inject import javax.inject.Inject


internal class AccessTokenInterceptor @Inject constructor(private val sessionParamsStore: SessionParamsStore) : Interceptor { internal class AccessTokenInterceptor @Inject constructor(private val credentials: Credentials) : Interceptor {


override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request() var request = chain.request()
val newRequestBuilder = request.newBuilder() val newRequestBuilder = request.newBuilder()
// Add the access token to all requests if it is set // Add the access token to all requests if it is set
val sessionParams = sessionParamsStore.get() newRequestBuilder.addHeader(HttpHeaders.Authorization, "Bearer " + credentials.accessToken)
sessionParams?.let {
newRequestBuilder.addHeader(HttpHeaders.Authorization, "Bearer " + it.credentials.accessToken)
}
request = newRequestBuilder.build() request = newRequestBuilder.build()
return chain.proceed(request) return chain.proceed(request)
} }

View File

@ -0,0 +1,39 @@
/*
*
* * Copyright 2019 New Vector Ltd
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/

package im.vector.matrix.android.internal.network

import com.squareup.moshi.Moshi
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import javax.inject.Inject

class RetrofitFactory @Inject constructor(private val moshi: Moshi) {

fun create(okHttpClient: OkHttpClient, baseUrl: String): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(UnitConverterFactory)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
}

}

View File

@ -28,13 +28,18 @@ import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.database.model.SessionRealmModule import im.vector.matrix.android.internal.database.model.SessionRealmModule
import im.vector.matrix.android.internal.di.Authenticated
import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
import im.vector.matrix.android.internal.session.room.prune.EventsPruner import im.vector.matrix.android.internal.session.room.prune.EventsPruner
import im.vector.matrix.android.internal.session.user.UserEntityUpdater import im.vector.matrix.android.internal.session.user.UserEntityUpdater
import im.vector.matrix.android.internal.util.md5 import im.vector.matrix.android.internal.util.md5
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import okhttp3.OkHttpClient
import retrofit2.Retrofit import retrofit2.Retrofit
import java.io.File import java.io.File


@ -86,13 +91,26 @@ internal abstract class SessionModule {
@JvmStatic @JvmStatic
@Provides @Provides
@SessionScope @SessionScope
fun providesRetrofit(sessionParams: SessionParams, retrofitBuilder: Retrofit.Builder): Retrofit { @Authenticated
return retrofitBuilder fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient,
.baseUrl(sessionParams.homeServerConnectionConfig.homeServerUri.toString()) accessTokenInterceptor: AccessTokenInterceptor): OkHttpClient {
return okHttpClient.newBuilder()
.addInterceptor(accessTokenInterceptor)
.build() .build()
} }

@JvmStatic
@Provides
@SessionScope
fun providesRetrofit(@Authenticated okHttpClient: OkHttpClient,
sessionParams: SessionParams,
retrofitFactory: RetrofitFactory): Retrofit {
return retrofitFactory
.create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString())
}
} }



@Binds @Binds
abstract fun bindSession(session: DefaultSession): Session abstract fun bindSession(session: DefaultSession): Session



View File

@ -20,7 +20,9 @@ import arrow.core.Try
import arrow.core.Try.Companion.raise import arrow.core.Try.Companion.raise
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.internal.di.Authenticated
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.network.ProgressRequestBody import im.vector.matrix.android.internal.network.ProgressRequestBody
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import okhttp3.* import okhttp3.*
@ -29,7 +31,8 @@ import java.io.IOException
import javax.inject.Inject import javax.inject.Inject




internal class FileUploader @Inject constructor(private val okHttpClient: OkHttpClient, internal class FileUploader @Inject constructor(@Authenticated
private val okHttpClient: OkHttpClient,
private val sessionParams: SessionParams, private val sessionParams: SessionParams,
private val moshi: Moshi) { private val moshi: Moshi) {



View File

@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.database.query.where
import io.realm.Realm import io.realm.Realm
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmQuery import io.realm.RealmQuery
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject


internal class SenderRoomMemberExtractor @Inject constructor(private val roomId: String) { internal class SenderRoomMemberExtractor @Inject constructor(private val roomId: String) {
@ -46,9 +47,8 @@ internal class SenderRoomMemberExtractor @Inject constructor(private val roomId:
event.stateIndex <= 0 -> baseQuery(chunkEntity.events, sender, unlinked).next(from = event.stateIndex)?.prevContent event.stateIndex <= 0 -> baseQuery(chunkEntity.events, sender, unlinked).next(from = event.stateIndex)?.prevContent
else -> baseQuery(chunkEntity.events, sender, unlinked).prev(since = event.stateIndex)?.content else -> baseQuery(chunkEntity.events, sender, unlinked).prev(since = event.stateIndex)?.content
} }

val fallbackContent = content val fallbackContent = content
?: baseQuery(roomEntity.untimelinedStateEvents, sender, unlinked).prev(since = event.stateIndex)?.content ?: baseQuery(roomEntity.untimelinedStateEvents, sender, unlinked).prev(since = event.stateIndex)?.content


return ContentMapper.map(fallbackContent).toModel() return ContentMapper.map(fallbackContent).toModel()
} }

View File

@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.signout


import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.signout.SignOutService import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
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

View File

@ -17,6 +17,9 @@
package im.vector.matrix.android.internal.session.signout package im.vector.matrix.android.internal.session.signout


import arrow.core.Try import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
@ -25,14 +28,20 @@ import javax.inject.Inject


internal interface SignOutTask : Task<Unit, Unit> internal interface SignOutTask : Task<Unit, Unit>


internal class DefaultSignOutTask @Inject constructor(private val signOutAPI: SignOutAPI, internal class DefaultSignOutTask @Inject constructor(private val credentials: Credentials,
private val signOutAPI: SignOutAPI,
private val sessionManager: SessionManager,
private val sessionParamsStore: SessionParamsStore) : SignOutTask { private val sessionParamsStore: SessionParamsStore) : SignOutTask {


override suspend fun execute(params: Unit): Try<Unit> { override suspend fun execute(params: Unit): Try<Unit> {
return executeRequest<Unit> { return executeRequest<Unit> {
apiCall = signOutAPI.signOut() apiCall = signOutAPI.signOut()
}.flatMap { }.flatMap {
sessionParamsStore.delete() sessionParamsStore.delete(credentials.userId)
}.flatMap {
Try {
sessionManager.releaseSession(credentials.userId)
}
} }
} }
} }

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.sync
import arrow.core.Try import arrow.core.Try
import arrow.core.failure import arrow.core.failure
import arrow.core.recoverWith import arrow.core.recoverWith
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
import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.SessionParamsStore
@ -36,6 +37,7 @@ internal interface SyncTask : Task<SyncTask.Params, SyncResponse> {
} }


internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI,
private val credentials: Credentials,
private val filterRepository: FilterRepository, private val filterRepository: FilterRepository,
private val syncResponseHandler: SyncResponseHandler, private val syncResponseHandler: SyncResponseHandler,
private val sessionParamsStore: SessionParamsStore private val sessionParamsStore: SessionParamsStore
@ -58,7 +60,7 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI,
// Intercept 401 // Intercept 401
if (throwable is Failure.ServerError if (throwable is Failure.ServerError
&& throwable.error.code == MatrixError.UNKNOWN_TOKEN) { && throwable.error.code == MatrixError.UNKNOWN_TOKEN) {
sessionParamsStore.delete() sessionParamsStore.delete(credentials.userId)
} }


// Transmit the throwable // Transmit the throwable

View File

@ -32,51 +32,55 @@ import com.github.piasy.biv.loader.glide.GlideImageLoader
import com.jakewharton.threetenabp.AndroidThreeTen import com.jakewharton.threetenabp.AndroidThreeTen
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixConfiguration import im.vector.matrix.android.api.MatrixConfiguration
import im.vector.riotredesign.core.di.DaggerVectorComponent import im.vector.riotredesign.core.di.*
import im.vector.riotredesign.core.di.HasInjector
import im.vector.riotredesign.core.di.VectorComponent
import im.vector.riotredesign.features.configuration.VectorConfiguration import im.vector.riotredesign.features.configuration.VectorConfiguration
import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks
import im.vector.riotredesign.features.rageshake.VectorFileLogger import im.vector.riotredesign.features.rageshake.VectorFileLogger
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.system.measureTimeMillis




class VectorApplication : Application(), HasInjector<VectorComponent>, MatrixConfiguration.Provider, androidx.work.Configuration.Provider { class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider {


lateinit var appContext: Context lateinit var appContext: Context
//font thread handler //font thread handler
@Inject lateinit var vectorConfiguration: VectorConfiguration @Inject lateinit var vectorConfiguration: VectorConfiguration
@Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider @Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
lateinit var vectorComponent: VectorComponent lateinit var vectorComponent: VectorComponent
private var fontThreadHandler: Handler? = null private var fontThreadHandler: Handler? = null


override fun onCreate() { override fun onCreate() {
super.onCreate() val time = measureTimeMillis {
appContext = this super.onCreate()
vectorComponent = DaggerVectorComponent.factory().create(this) appContext = this
vectorComponent.inject(this) vectorComponent = DaggerVectorComponent.factory().create(this)
VectorUncaughtExceptionHandler.activate(this) vectorComponent.inject(this)
// Log vectorUncaughtExceptionHandler.activate(this)
VectorFileLogger.init(this) // Log
Timber.plant(Timber.DebugTree(), VectorFileLogger) VectorFileLogger.init(this)
if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree(), VectorFileLogger)
Stetho.initializeWithDefaults(this) if (BuildConfig.DEBUG) {
Stetho.initializeWithDefaults(this)
}
AndroidThreeTen.init(this)
BigImageViewer.initialize(GlideImageLoader.with(applicationContext))
EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks())
val fontRequest = FontRequest(
"com.google.android.gms.fonts",
"com.google.android.gms",
"Noto Color Emoji Compat",
R.array.com_google_android_gms_fonts_certs
)
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
vectorConfiguration.initConfiguration()
} }
AndroidThreeTen.init(this) Timber.v("On create took $time ms")
BigImageViewer.initialize(GlideImageLoader.with(applicationContext))
EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks())
val fontRequest = FontRequest(
"com.google.android.gms.fonts",
"com.google.android.gms",
"Noto Color Emoji Compat",
R.array.com_google_android_gms_fonts_certs
)
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
vectorConfiguration.initConfiguration()
} }


override fun providesMatrixConfiguration() = MatrixConfiguration(BuildConfig.FLAVOR_DESCRIPTION) override fun providesMatrixConfiguration() = MatrixConfiguration(BuildConfig.FLAVOR_DESCRIPTION)

View File

@ -0,0 +1,57 @@
/*
*
* * Copyright 2019 New Vector Ltd
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/

package im.vector.riotredesign.core.di

import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.sync.FilterService
import java.lang.IllegalStateException
import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ActiveSessionHolder @Inject constructor(private val authenticator: Authenticator) {

private var activeSession: AtomicReference<Session?> = AtomicReference()

fun setActiveSession(session: Session){
activeSession.set(session)
}

fun clearActiveSession(){
activeSession.set(null)
}

fun hasActiveSession(): Boolean {
return activeSession.get() != null
}

fun getActiveSession(): Session {
return activeSession.get() ?: throw IllegalStateException("You should authenticate before using this")
}

//TODO: Stop sync ?
fun switchToSession(sessionParams: SessionParams) {
val newActiveSession = authenticator.getSession(sessionParams)
activeSession.set(newActiveSession)
}

}

View File

@ -16,8 +16,8 @@


package im.vector.riotredesign.core.di package im.vector.riotredesign.core.di


interface HasInjector<C> { interface HasScreenInjector {


fun injector(): C fun injector(): ScreenComponent


} }

View File

@ -0,0 +1,25 @@
/*
*
* * Copyright 2019 New Vector Ltd
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/

package im.vector.riotredesign.core.di

interface HasVectorInjector {

fun injector(): VectorComponent

}

View File

@ -31,10 +31,7 @@ import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupSt
import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupStep2Fragment import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupStep2Fragment
import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupStep3Fragment import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupStep3Fragment
import im.vector.riotredesign.features.crypto.verification.SASVerificationIncomingFragment import im.vector.riotredesign.features.crypto.verification.SASVerificationIncomingFragment
import im.vector.riotredesign.features.home.HomeActivity import im.vector.riotredesign.features.home.*
import im.vector.riotredesign.features.home.HomeDetailFragment
import im.vector.riotredesign.features.home.HomeDrawerFragment
import im.vector.riotredesign.features.home.HomeModule
import im.vector.riotredesign.features.home.group.GroupListFragment import im.vector.riotredesign.features.home.group.GroupListFragment
import im.vector.riotredesign.features.home.room.detail.RoomDetailFragment import im.vector.riotredesign.features.home.room.detail.RoomDetailFragment
import im.vector.riotredesign.features.home.room.detail.timeline.action.MessageActionsBottomSheet import im.vector.riotredesign.features.home.room.detail.timeline.action.MessageActionsBottomSheet
@ -42,7 +39,13 @@ import im.vector.riotredesign.features.home.room.detail.timeline.action.MessageM
import im.vector.riotredesign.features.home.room.detail.timeline.action.QuickReactionFragment import im.vector.riotredesign.features.home.room.detail.timeline.action.QuickReactionFragment
import im.vector.riotredesign.features.home.room.detail.timeline.action.ViewReactionBottomSheet import im.vector.riotredesign.features.home.room.detail.timeline.action.ViewReactionBottomSheet
import im.vector.riotredesign.features.home.room.list.RoomListFragment import im.vector.riotredesign.features.home.room.list.RoomListFragment
import im.vector.riotredesign.features.invite.VectorInviteView
import im.vector.riotredesign.features.login.LoginActivity import im.vector.riotredesign.features.login.LoginActivity
import im.vector.riotredesign.features.media.ImageMediaViewerActivity
import im.vector.riotredesign.features.media.VideoMediaViewerActivity
import im.vector.riotredesign.features.rageshake.BugReportActivity
import im.vector.riotredesign.features.rageshake.BugReporter
import im.vector.riotredesign.features.rageshake.RageShake
import im.vector.riotredesign.features.reactions.EmojiReactionPickerActivity import im.vector.riotredesign.features.reactions.EmojiReactionPickerActivity
import im.vector.riotredesign.features.roomdirectory.PublicRoomsFragment import im.vector.riotredesign.features.roomdirectory.PublicRoomsFragment
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryActivity import im.vector.riotredesign.features.roomdirectory.RoomDirectoryActivity
@ -60,6 +63,10 @@ interface ScreenComponent {


fun viewModelFactory(): ViewModelProvider.Factory fun viewModelFactory(): ViewModelProvider.Factory


fun bugReporter(): BugReporter

fun rageShake(): RageShake

fun inject(activity: HomeActivity) fun inject(activity: HomeActivity)


fun inject(roomDetailFragment: RoomDetailFragment) fun inject(roomDetailFragment: RoomDetailFragment)
@ -118,6 +125,15 @@ interface ScreenComponent {


fun inject(roomDirectoryActivity: RoomDirectoryActivity) fun inject(roomDirectoryActivity: RoomDirectoryActivity)


fun inject(bugReportActivity: BugReportActivity)

fun inject(imageMediaViewerActivity: ImageMediaViewerActivity)

fun inject(vectorInviteView: VectorInviteView)

fun inject(videoMediaViewerActivity: VideoMediaViewerActivity)


@Component.Factory @Component.Factory
interface Factory { interface Factory {
fun create(vectorComponent: VectorComponent, fun create(vectorComponent: VectorComponent,

View File

@ -28,17 +28,23 @@ import im.vector.riotredesign.VectorApplication
import im.vector.riotredesign.features.configuration.VectorConfiguration import im.vector.riotredesign.features.configuration.VectorConfiguration
import im.vector.riotredesign.features.crypto.keysrequest.KeyRequestHandler import im.vector.riotredesign.features.crypto.keysrequest.KeyRequestHandler
import im.vector.riotredesign.features.crypto.verification.IncomingVerificationRequestHandler import im.vector.riotredesign.features.crypto.verification.IncomingVerificationRequestHandler
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.HomeNavigator import im.vector.riotredesign.features.home.HomeNavigator
import im.vector.riotredesign.features.home.HomeRoomListObservableStore import im.vector.riotredesign.features.home.HomeRoomListObservableStore
import im.vector.riotredesign.features.home.group.SelectedGroupStore import im.vector.riotredesign.features.home.group.SelectedGroupStore
import im.vector.riotredesign.features.navigation.Navigator import im.vector.riotredesign.features.navigation.Navigator
import im.vector.riotredesign.features.notifications.NotificationDrawerManager import im.vector.riotredesign.features.notifications.NotificationDrawerManager
import im.vector.riotredesign.features.rageshake.BugReporter
import im.vector.riotredesign.features.rageshake.RageShake
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
import javax.inject.Singleton import javax.inject.Singleton


@Component(modules = [VectorModule::class]) @Component(modules = [VectorModule::class])
@Singleton @Singleton
interface VectorComponent { interface VectorComponent {


fun inject(vectorApplication: VectorApplication)

fun matrix(): Matrix fun matrix(): Matrix


fun currentSession(): Session fun currentSession(): Session
@ -51,6 +57,8 @@ interface VectorComponent {


fun vectorConfiguration(): VectorConfiguration fun vectorConfiguration(): VectorConfiguration


fun activeSessionHolder(): ActiveSessionHolder

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


fun navigator(): Navigator fun navigator(): Navigator
@ -65,10 +73,12 @@ interface VectorComponent {


fun incomingKeyRequestHandler(): KeyRequestHandler fun incomingKeyRequestHandler(): KeyRequestHandler


fun inject(vectorApplication: VectorApplication)

fun authenticator(): Authenticator fun authenticator(): Authenticator


fun bugReporter(): BugReporter

fun vectorUncaughtExceptionHandler(): VectorUncaughtExceptionHandler

@Component.Factory @Component.Factory
interface Factory { interface Factory {
fun create(@BindsInstance context: Context): VectorComponent fun create(@BindsInstance context: Context): VectorComponent

View File

@ -55,9 +55,9 @@ abstract class VectorModule {


@Provides @Provides
@JvmStatic @JvmStatic
fun providesCurrentSession(matrix: Matrix): Session { fun providesCurrentSession(activeSessionHolder: ActiveSessionHolder): Session {
//TODO: handle session injection better //TODO: handle session injection better
return matrix.currentSession!! return activeSessionHolder.getActiveSession()
} }


@Provides @Provides

View File

@ -0,0 +1,28 @@
/*
*
* * Copyright 2019 New Vector Ltd
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/

package im.vector.riotredesign.core.extensions

import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.sync.FilterService

fun Session.openAndStartSync(){
open()
setFilter(FilterService.FilterPreset.RiotFilter)
startSync()
}

View File

@ -32,40 +32,32 @@ import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import butterknife.BindView import butterknife.BindView
import butterknife.ButterKnife import butterknife.ButterKnife
import butterknife.Unbinder import butterknife.Unbinder
import com.airbnb.mvrx.BaseMvRxActivity import com.airbnb.mvrx.BaseMvRxActivity
import com.airbnb.mvrx.MvRxState
import com.bumptech.glide.util.Util import com.bumptech.glide.util.Util
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import im.vector.riotredesign.BuildConfig import im.vector.riotredesign.BuildConfig
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.DaggerScreenComponent import im.vector.riotredesign.core.di.*
import im.vector.riotredesign.core.di.HasInjector
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.di.VectorComponent
import im.vector.riotredesign.core.utils.toast import im.vector.riotredesign.core.utils.toast
import im.vector.riotredesign.features.configuration.VectorConfiguration import im.vector.riotredesign.features.configuration.VectorConfiguration
import im.vector.riotredesign.features.rageshake.BugReportActivity import im.vector.riotredesign.features.rageshake.BugReportActivity
import im.vector.riotredesign.features.rageshake.BugReporter import im.vector.riotredesign.features.rageshake.BugReporter
import im.vector.riotredesign.features.rageshake.RageShake import im.vector.riotredesign.features.rageshake.RageShake
import im.vector.riotredesign.features.roomdirectory.PublicRoomsViewState
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryViewModel
import im.vector.riotredesign.features.themes.ActivityOtherThemes import im.vector.riotredesign.features.themes.ActivityOtherThemes
import im.vector.riotredesign.features.themes.ThemeUtils import im.vector.riotredesign.features.themes.ThemeUtils
import im.vector.riotredesign.receivers.DebugReceiver import im.vector.riotredesign.receivers.DebugReceiver
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import timber.log.Timber import timber.log.Timber
import javax.inject.Provider import kotlin.system.measureTimeMillis
import kotlin.reflect.KClass




abstract class VectorBaseActivity : BaseMvRxActivity(), HasInjector<ScreenComponent> { abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
/* ========================================================================================== /* ==========================================================================================
* UI * UI
* ========================================================================================== */ * ========================================================================================== */
@ -81,6 +73,8 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasInjector<ScreenCompon


protected lateinit var viewModelFactory: ViewModelProvider.Factory protected lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var configurationViewModel: ConfigurationViewModel private lateinit var configurationViewModel: ConfigurationViewModel
protected lateinit var bugReporter: BugReporter
private lateinit var rageShake: RageShake


private var unBinder: Unbinder? = null private var unBinder: Unbinder? = null


@ -92,7 +86,6 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasInjector<ScreenCompon
private val uiDisposables = CompositeDisposable() private val uiDisposables = CompositeDisposable()
private val restorables = ArrayList<Restorable>() private val restorables = ArrayList<Restorable>()


private var rageShake: RageShake? = null
private lateinit var screenComponent: ScreenComponent private lateinit var screenComponent: ScreenComponent


override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {
@ -125,9 +118,14 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasInjector<ScreenCompon
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
screenComponent = DaggerScreenComponent.factory().create(getVectorComponent(), this) screenComponent = DaggerScreenComponent.factory().create(getVectorComponent(), this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
injectWith(screenComponent) val timeForInjection = measureTimeMillis {
injectWith(screenComponent)
}
Timber.v("Injecting dependencies into ${javaClass.simpleName} took $timeForInjection ms")
viewModelFactory = screenComponent.viewModelFactory() viewModelFactory = screenComponent.viewModelFactory()
configurationViewModel = ViewModelProviders.of(this, viewModelFactory).get(ConfigurationViewModel::class.java) configurationViewModel = ViewModelProviders.of(this, viewModelFactory).get(ConfigurationViewModel::class.java)
bugReporter = screenComponent.bugReporter()
rageShake = screenComponent.rageShake()
configurationViewModel.activityRestarter.observe(this, Observer { configurationViewModel.activityRestarter.observe(this, Observer {
if (!it.hasBeenHandled) { if (!it.hasBeenHandled) {
// Recreate the Activity because configuration has changed // Recreate the Activity because configuration has changed
@ -137,7 +135,6 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasInjector<ScreenCompon
}) })


// Shake detector // Shake detector
rageShake = RageShake(this)


ThemeUtils.setActivityTheme(this, getOtherThemes()) ThemeUtils.setActivityTheme(this, getOtherThemes())


@ -215,7 +212,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasInjector<ScreenCompon
super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig) super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig)


Timber.w("onMultiWindowModeChanged. isInMultiWindowMode: $isInMultiWindowMode") Timber.w("onMultiWindowModeChanged. isInMultiWindowMode: $isInMultiWindowMode")
BugReporter.inMultiWindowMode = isInMultiWindowMode bugReporter.inMultiWindowMode = isInMultiWindowMode
} }




@ -230,7 +227,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasInjector<ScreenCompon
* ========================================================================================== */ * ========================================================================================== */


internal fun getVectorComponent(): VectorComponent { internal fun getVectorComponent(): VectorComponent {
return (application as HasInjector<VectorComponent>).injector() return (application as HasVectorInjector).injector()
} }


/** /**

View File

@ -35,14 +35,14 @@ import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import com.bumptech.glide.util.Util.assertMainThread import com.bumptech.glide.util.Util.assertMainThread
import im.vector.riotredesign.core.di.DaggerScreenComponent import im.vector.riotredesign.core.di.DaggerScreenComponent
import im.vector.riotredesign.core.di.HasInjector import im.vector.riotredesign.core.di.HasScreenInjector
import im.vector.riotredesign.core.di.ScreenComponent import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.features.navigation.Navigator import im.vector.riotredesign.features.navigation.Navigator
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import timber.log.Timber import timber.log.Timber


abstract class VectorBaseFragment : BaseMvRxFragment(), OnBackPressed, HasInjector<ScreenComponent> { abstract class VectorBaseFragment : BaseMvRxFragment(), OnBackPressed, HasScreenInjector {


// Butterknife unbinder // Butterknife unbinder
private var mUnBinder: Unbinder? = null private var mUnBinder: Unbinder? = null

View File

@ -22,7 +22,9 @@ import android.os.Bundle
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.Authenticator import im.vector.matrix.android.api.auth.Authenticator
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.di.ScreenComponent import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.extensions.openAndStartSync
import im.vector.riotredesign.core.platform.VectorBaseActivity import im.vector.riotredesign.core.platform.VectorBaseActivity
import im.vector.riotredesign.features.home.HomeActivity import im.vector.riotredesign.features.home.HomeActivity
import im.vector.riotredesign.features.login.LoginActivity import im.vector.riotredesign.features.login.LoginActivity
@ -49,6 +51,7 @@ class MainActivity : VectorBaseActivity() {


@Inject lateinit var matrix: Matrix @Inject lateinit var matrix: Matrix
@Inject lateinit var authenticator: Authenticator @Inject lateinit var authenticator: Authenticator
@Inject lateinit var sessionHolder: ActiveSessionHolder


override fun injectWith(injector: ScreenComponent) { override fun injectWith(injector: ScreenComponent) {
injector.inject(this) injector.inject(this)
@ -58,30 +61,31 @@ class MainActivity : VectorBaseActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val clearCache = intent.getBooleanExtra(EXTRA_CLEAR_CACHE, false) val clearCache = intent.getBooleanExtra(EXTRA_CLEAR_CACHE, false)
val clearCredentials = intent.getBooleanExtra(EXTRA_CLEAR_CREDENTIALS, false) val clearCredentials = intent.getBooleanExtra(EXTRA_CLEAR_CREDENTIALS, false)
val session = matrix.currentSession // Handle some wanted cleanup
if (session == null) { when {
start() clearCredentials -> sessionHolder.getActiveSession().signOut(object : MatrixCallback<Unit> {
} else { override fun onSuccess(data: Unit) {
// Handle some wanted cleanup Timber.w("SIGN_OUT: success, start app")
when { sessionHolder.clearActiveSession()
clearCredentials -> session.signOut(object : MatrixCallback<Unit> { start()
override fun onSuccess(data: Unit) { }
Timber.w("SIGN_OUT: success, start app") })
start() clearCache -> sessionHolder.getActiveSession().clearCache(object : MatrixCallback<Unit> {
} override fun onSuccess(data: Unit) {
}) start()
clearCache -> session.clearCache(object : MatrixCallback<Unit> { }
override fun onSuccess(data: Unit) { })
start() else -> start()
}
})
else -> start()
}
} }
} }


private fun start() { private fun start() {
val intent = if (authenticator.hasActiveSessions()) { val intent = if (authenticator.hasAuthenticatedSessions()) {
if (!sessionHolder.hasActiveSession()) {
val lastAuthenticatedSession = authenticator.getLastAuthenticatedSession()!!
sessionHolder.setActiveSession(lastAuthenticatedSession)
lastAuthenticatedSession.openAndStartSync()
}
HomeActivity.newIntent(this) HomeActivity.newIntent(this)
} else { } else {
LoginActivity.newIntent(this) LoginActivity.newIntent(this)

View File

@ -29,6 +29,7 @@ import im.vector.riotredesign.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_autocomplete_user) @EpoxyModelClass(layout = R.layout.item_autocomplete_user)
abstract class AutocompleteUserItem : VectorEpoxyModel<AutocompleteUserItem.Holder>() { abstract class AutocompleteUserItem : VectorEpoxyModel<AutocompleteUserItem.Holder>() {


@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute var name: String? = null @EpoxyAttribute var name: String? = null
@EpoxyAttribute var userId: String = "" @EpoxyAttribute var userId: String = ""
@EpoxyAttribute var avatarUrl: String? = null @EpoxyAttribute var avatarUrl: String? = null
@ -37,7 +38,7 @@ abstract class AutocompleteUserItem : VectorEpoxyModel<AutocompleteUserItem.Hold
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
holder.view.setOnClickListener(clickListener) holder.view.setOnClickListener(clickListener)
holder.nameView.text = name holder.nameView.text = name
AvatarRenderer.render(avatarUrl, userId, name, holder.avatarImageView) avatarRenderer.render(avatarUrl, userId, name, holder.avatarImageView)
} }


class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {

View File

@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.features.popup.PopupAlertManager import im.vector.riotredesign.features.popup.PopupAlertManager
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -31,11 +32,11 @@ import javax.inject.Singleton
*/ */
@Singleton @Singleton
class IncomingVerificationRequestHandler @Inject constructor(val context: Context, class IncomingVerificationRequestHandler @Inject constructor(val context: Context,
private val session: Session private val activeSessionHolder: ActiveSessionHolder
) : SasVerificationService.SasVerificationListener { ) : SasVerificationService.SasVerificationListener {


init { init {
session.getSasVerificationService().addListener(this) activeSessionHolder.getActiveSession().getSasVerificationService().addListener(this)
} }


override fun transactionCreated(tx: SasVerificationTransaction) {} override fun transactionCreated(tx: SasVerificationTransaction) {}
@ -44,7 +45,7 @@ class IncomingVerificationRequestHandler @Inject constructor(val context: Contex
when (tx.state) { when (tx.state) {
SasVerificationTxState.OnStarted -> { SasVerificationTxState.OnStarted -> {
//Add a notification for every incoming request //Add a notification for every incoming request
val session = Matrix.getInstance(context).currentSession!! val session = activeSessionHolder.getActiveSession()
val name = session.getUser(tx.otherUserId)?.displayName val name = session.getUser(tx.otherUserId)?.displayName
?: tx.otherUserId ?: tx.otherUserId



View File

@ -27,6 +27,7 @@ import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ScreenComponent import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.AvatarRenderer
import javax.inject.Inject


class SASVerificationIncomingFragment : VectorBaseFragment() { class SASVerificationIncomingFragment : VectorBaseFragment() {


@ -48,6 +49,7 @@ class SASVerificationIncomingFragment : VectorBaseFragment() {


override fun getLayoutResId() = R.layout.fragment_sas_verification_incoming_request override fun getLayoutResId() = R.layout.fragment_sas_verification_incoming_request


@Inject lateinit var avatarRenderer: AvatarRenderer
private lateinit var viewModel: SasVerificationViewModel private lateinit var viewModel: SasVerificationViewModel


override fun injectWith(injector: ScreenComponent) { override fun injectWith(injector: ScreenComponent) {
@ -66,10 +68,10 @@ class SASVerificationIncomingFragment : VectorBaseFragment() {
otherDeviceTextView.text = viewModel.otherDeviceId otherDeviceTextView.text = viewModel.otherDeviceId


viewModel.otherUser?.let { viewModel.otherUser?.let {
AvatarRenderer.render(it, avatarImageView) avatarRenderer.render(it, avatarImageView)
} ?: run { } ?: run {
// Fallback to what we know // Fallback to what we know
AvatarRenderer.render(null, viewModel.otherUserId ?: "", viewModel.otherUserId, avatarImageView) avatarRenderer.render(null, viewModel.otherUserId ?: "", viewModel.otherUserId, avatarImageView)
} }


viewModel.transactionState.observe(this, Observer { viewModel.transactionState.observe(this, Observer {

View File

@ -26,29 +26,32 @@ import com.amulyakhare.textdrawable.TextDrawable
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.DrawableImageViewTarget import com.bumptech.glide.request.target.DrawableImageViewTarget
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.MatrixPatterns
import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.content.ContentUrlResolver
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.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.glide.GlideRequest import im.vector.riotredesign.core.glide.GlideRequest
import im.vector.riotredesign.core.glide.GlideRequests import im.vector.riotredesign.core.glide.GlideRequests
import javax.inject.Inject


/** /**
* This helper centralise ways to retrieve avatar into ImageView or even generic Target<Drawable> * This helper centralise ways to retrieve avatar into ImageView or even generic Target<Drawable>
*/ */


object AvatarRenderer { class AvatarRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder){


private const val THUMBNAIL_SIZE = 250 companion object {
private const val THUMBNAIL_SIZE = 250


private val AVATAR_COLOR_LIST = listOf( private val AVATAR_COLOR_LIST = listOf(
R.color.riotx_avatar_fill_1, R.color.riotx_avatar_fill_1,
R.color.riotx_avatar_fill_2, R.color.riotx_avatar_fill_2,
R.color.riotx_avatar_fill_3 R.color.riotx_avatar_fill_3
) )
}


@UiThread @UiThread
fun render(roomSummary: RoomSummary, imageView: ImageView) { fun render(roomSummary: RoomSummary, imageView: ImageView) {
@ -78,7 +81,7 @@ object AvatarRenderer {
name name
} }
val placeholder = getPlaceholderDrawable(context, identifier, displayName) val placeholder = getPlaceholderDrawable(context, identifier, displayName)
buildGlideRequest(context, glideRequest, avatarUrl) buildGlideRequest(glideRequest, avatarUrl)
.placeholder(placeholder) .placeholder(placeholder)
.into(target) .into(target)
} }
@ -115,8 +118,8 @@ object AvatarRenderer {
// return AVATAR_COLOR_LIST[colorIndex.toInt()] // return AVATAR_COLOR_LIST[colorIndex.toInt()]
// } // }


private fun buildGlideRequest(context: Context, glideRequest: GlideRequests, avatarUrl: String?): GlideRequest<Drawable> { private fun buildGlideRequest(glideRequest: GlideRequests, avatarUrl: String?): GlideRequest<Drawable> {
val resolvedUrl = Matrix.getInstance(context).currentSession!!.contentUrlResolver() val resolvedUrl = activeSessionHolder.getActiveSession().contentUrlResolver()
.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE) .resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)


return glideRequest return glideRequest

View File

@ -31,6 +31,7 @@ import androidx.lifecycle.ViewModelProviders
import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.viewModel
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.di.ScreenComponent import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.extensions.hideKeyboard import im.vector.riotredesign.core.extensions.hideKeyboard
import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.extensions.observeEvent
@ -57,8 +58,10 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
private val homeActivityViewModel: HomeActivityViewModel by viewModel() private val homeActivityViewModel: HomeActivityViewModel by viewModel()
private lateinit var navigationViewModel: HomeNavigationViewModel private lateinit var navigationViewModel: HomeNavigationViewModel


@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var homeActivityViewModelFactory: HomeActivityViewModel.Factory @Inject lateinit var homeActivityViewModelFactory: HomeActivityViewModel.Factory
@Inject lateinit var homeNavigator: HomeNavigator @Inject lateinit var homeNavigator: HomeNavigator
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
// TODO Move this elsewhere // TODO Move this elsewhere
@Inject lateinit var incomingVerificationRequestHandler: IncomingVerificationRequestHandler @Inject lateinit var incomingVerificationRequestHandler: IncomingVerificationRequestHandler
// TODO Move this elsewhere // TODO Move this elsewhere
@ -119,14 +122,14 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()


if (VectorUncaughtExceptionHandler.didAppCrash(this)) { if (vectorUncaughtExceptionHandler.didAppCrash(this)) {
VectorUncaughtExceptionHandler.clearAppCrashStatus(this) vectorUncaughtExceptionHandler.clearAppCrashStatus(this)


AlertDialog.Builder(this) AlertDialog.Builder(this)
.setMessage(R.string.send_bug_report_app_crashed) .setMessage(R.string.send_bug_report_app_crashed)
.setCancelable(false) .setCancelable(false)
.setPositiveButton(R.string.yes) { _, _ -> BugReporter.openBugReportScreen(this) } .setPositiveButton(R.string.yes) { _, _ -> bugReporter.openBugReportScreen(this) }
.setNegativeButton(R.string.no) { _, _ -> BugReporter.deleteCrashFile(this) } .setNegativeButton(R.string.no) { _, _ -> bugReporter.deleteCrashFile(this) }
.show() .show()
} }
} }
@ -140,7 +143,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.sliding_menu_sign_out -> { R.id.sliding_menu_sign_out -> {
SignOutUiWorker(this).perform(Matrix.getInstance(this).currentSession!!) SignOutUiWorker(this).perform(activeSessionHolder.getActiveSession())
return true return true
} }
} }

View File

@ -70,6 +70,7 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate {


@Inject lateinit var session: Session @Inject lateinit var session: Session
@Inject lateinit var homeDetailViewModelFactory: HomeDetailViewModel.Factory @Inject lateinit var homeDetailViewModelFactory: HomeDetailViewModel.Factory
@Inject lateinit var avatarRenderer: AvatarRenderer


override fun getLayoutResId(): Int { override fun getLayoutResId(): Int {
return R.layout.fragment_home_detail return R.layout.fragment_home_detail
@ -138,7 +139,7 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate {
parentActivity.configure(groupToolbar) parentActivity.configure(groupToolbar)
} }
groupToolbar.title = "" groupToolbar.title = ""
AvatarRenderer.render( avatarRenderer.render(
params.groupAvatar, params.groupAvatar,
params.groupId, params.groupId,
params.groupName, params.groupName,

View File

@ -37,6 +37,7 @@ class HomeDrawerFragment : VectorBaseFragment() {
} }


@Inject lateinit var session: Session @Inject lateinit var session: Session
@Inject lateinit var avatarRenderer: AvatarRenderer


override fun getLayoutResId() = R.layout.fragment_home_drawer override fun getLayoutResId() = R.layout.fragment_home_drawer


@ -53,7 +54,7 @@ class HomeDrawerFragment : VectorBaseFragment() {


session.observeUser(session.sessionParams.credentials.userId).observeK(this) { user -> session.observeUser(session.sessionParams.credentials.userId).observeK(this) { user ->
if (user != null) { if (user != null) {
AvatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView) avatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView)
homeDrawerUsernameView.text = user.displayName homeDrawerUsernameView.text = user.displayName
homeDrawerUserIdView.text = user.userId homeDrawerUserIdView.text = user.userId
} }

View File

@ -18,9 +18,10 @@ package im.vector.riotredesign.features.home.group


import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.riotredesign.features.home.AvatarRenderer
import javax.inject.Inject import javax.inject.Inject


class GroupSummaryController @Inject constructor(): TypedEpoxyController<GroupListViewState>() { class GroupSummaryController @Inject constructor(private val avatarRenderer: AvatarRenderer): TypedEpoxyController<GroupListViewState>() {


var callback: Callback? = null var callback: Callback? = null


@ -35,6 +36,7 @@ class GroupSummaryController @Inject constructor(): TypedEpoxyController<GroupLi
summaries.forEach { groupSummary -> summaries.forEach { groupSummary ->
val isSelected = groupSummary.groupId == selected?.groupId val isSelected = groupSummary.groupId == selected?.groupId
groupSummaryItem { groupSummaryItem {
avatarRenderer(avatarRenderer)
id(groupSummary.groupId) id(groupSummary.groupId)
groupId(groupSummary.groupId) groupId(groupSummary.groupId)
groupName(groupSummary.displayName) groupName(groupSummary.displayName)

View File

@ -29,6 +29,7 @@ import im.vector.riotredesign.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_group) @EpoxyModelClass(layout = R.layout.item_group)
abstract class GroupSummaryItem : VectorEpoxyModel<GroupSummaryItem.Holder>() { abstract class GroupSummaryItem : VectorEpoxyModel<GroupSummaryItem.Holder>() {


@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var groupName: CharSequence @EpoxyAttribute lateinit var groupName: CharSequence
@EpoxyAttribute lateinit var groupId: String @EpoxyAttribute lateinit var groupId: String
@EpoxyAttribute var avatarUrl: String? = null @EpoxyAttribute var avatarUrl: String? = null
@ -40,7 +41,7 @@ abstract class GroupSummaryItem : VectorEpoxyModel<GroupSummaryItem.Holder>() {
holder.rootView.setOnClickListener { listener?.invoke() } holder.rootView.setOnClickListener { listener?.invoke() }
holder.groupNameView.text = groupName holder.groupNameView.text = groupName
holder.rootView.isChecked = selected holder.rootView.isChecked = selected
AvatarRenderer.render(avatarUrl, groupId, groupName.toString(), holder.avatarImageView) avatarRenderer.render(avatarUrl, groupId, groupName.toString(), holder.avatarImageView)
} }


class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {

View File

@ -174,6 +174,7 @@ class RoomDetailFragment :
private val textComposerViewModel: TextComposerViewModel by fragmentViewModel() private val textComposerViewModel: TextComposerViewModel by fragmentViewModel()


@Inject lateinit var session: Session @Inject lateinit var session: Session
@Inject lateinit var avatarRenderer: AvatarRenderer
@Inject lateinit var timelineEventController: TimelineEventController @Inject lateinit var timelineEventController: TimelineEventController
@Inject lateinit var commandAutocompletePolicy: CommandAutocompletePolicy @Inject lateinit var commandAutocompletePolicy: CommandAutocompletePolicy
@Inject lateinit var autocompleteCommandPresenter: AutocompleteCommandPresenter @Inject lateinit var autocompleteCommandPresenter: AutocompleteCommandPresenter
@ -183,6 +184,7 @@ class RoomDetailFragment :
@Inject lateinit var textComposerViewModelFactory: TextComposerViewModel.Factory @Inject lateinit var textComposerViewModelFactory: TextComposerViewModel.Factory
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback



override fun getLayoutResId() = R.layout.fragment_room_detail override fun getLayoutResId() = R.layout.fragment_room_detail


private lateinit var actionViewModel: ActionsHandler private lateinit var actionViewModel: ActionsHandler
@ -223,7 +225,7 @@ class RoomDetailFragment :
commandAutocompletePolicy.enabled = true commandAutocompletePolicy.enabled = true
val uid = session.sessionParams.credentials.userId val uid = session.sessionParams.credentials.userId
val meMember = session.getRoom(roomId)?.getRoomMember(uid) val meMember = session.getRoom(roomId)?.getRoomMember(uid)
AvatarRenderer.render(meMember?.avatarUrl, uid, meMember?.displayName, composerLayout.composerAvatarImageView) avatarRenderer.render(meMember?.avatarUrl, uid, meMember?.displayName, composerLayout.composerAvatarImageView)
composerLayout.collapse() composerLayout.collapse()
} }
SendMode.EDIT, SendMode.EDIT,
@ -270,7 +272,7 @@ class RoomDetailFragment :
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_reply)) composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_reply))
} }


AvatarRenderer.render(event.senderAvatar, event.root.sender avatarRenderer.render(event.senderAvatar, event.root.sender
?: "", event.senderName, composerLayout.composerRelatedMessageAvatar) ?: "", event.senderName, composerLayout.composerRelatedMessageAvatar)


composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text.length) composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text.length)
@ -378,7 +380,7 @@ class RoomDetailFragment :


// Add the span // Add the span
val user = session.getUser(item.userId) val user = session.getUser(item.userId)
val span = PillImageSpan(glideRequests, requireContext(), item.userId, user) val span = PillImageSpan(glideRequests, avatarRenderer, requireContext(), item.userId, user)
span.bind(composerLayout.composerEditText) span.bind(composerLayout.composerEditText)


editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
@ -487,7 +489,7 @@ class RoomDetailFragment :


val uid = session.sessionParams.credentials.userId val uid = session.sessionParams.credentials.userId
val meMember = session.getRoom(state.roomId)?.getRoomMember(uid) val meMember = session.getRoom(state.roomId)?.getRoomMember(uid)
AvatarRenderer.render(meMember?.avatarUrl, uid, meMember?.displayName, composerLayout.composerAvatarImageView) avatarRenderer.render(meMember?.avatarUrl, uid, meMember?.displayName, composerLayout.composerAvatarImageView)


} else if (summary?.membership == Membership.INVITE && inviter != null) { } else if (summary?.membership == Membership.INVITE && inviter != null) {
inviteView.visibility = View.VISIBLE inviteView.visibility = View.VISIBLE
@ -500,7 +502,7 @@ class RoomDetailFragment :
private fun renderRoomSummary(state: RoomDetailViewState) { private fun renderRoomSummary(state: RoomDetailViewState) {
state.asyncRoomSummary()?.let { state.asyncRoomSummary()?.let {
roomToolbarTitleView.text = it.displayName roomToolbarTitleView.text = it.displayName
AvatarRenderer.render(it, roomToolbarAvatarImageView) avatarRenderer.render(it, roomToolbarAvatarImageView)
if (it.topic.isNotEmpty()) { if (it.topic.isNotEmpty()) {
roomToolbarSubtitleView.visibility = View.VISIBLE roomToolbarSubtitleView.visibility = View.VISIBLE
roomToolbarSubtitleView.text = it.topic roomToolbarSubtitleView.text = it.topic

View File

@ -25,25 +25,14 @@ import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyController
import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyModel
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.MessageAudioContent import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.api.session.room.model.message.MessageContent
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.MessageVideoContent
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.core.epoxy.LoadingItem_ import im.vector.riotredesign.core.epoxy.LoadingItem_
import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.room.detail.timeline.factory.TimelineItemFactory import im.vector.riotredesign.features.home.room.detail.timeline.factory.TimelineItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.*
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotredesign.features.home.room.detail.timeline.helper.canBeMerged
import im.vector.riotredesign.features.home.room.detail.timeline.helper.nextDisplayableEvent
import im.vector.riotredesign.features.home.room.detail.timeline.helper.prevSameTypeEvents
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName
import im.vector.riotredesign.features.home.room.detail.timeline.item.DaySeparatorItem import im.vector.riotredesign.features.home.room.detail.timeline.item.DaySeparatorItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.DaySeparatorItem_ import im.vector.riotredesign.features.home.room.detail.timeline.item.DaySeparatorItem_
import im.vector.riotredesign.features.home.room.detail.timeline.item.MergedHeaderItem import im.vector.riotredesign.features.home.room.detail.timeline.item.MergedHeaderItem
@ -56,7 +45,9 @@ import javax.inject.Inject
class TimelineEventController @Inject constructor(private val dateFormatter: TimelineDateFormatter, class TimelineEventController @Inject constructor(private val dateFormatter: TimelineDateFormatter,
private val timelineItemFactory: TimelineItemFactory, private val timelineItemFactory: TimelineItemFactory,
private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
@TimelineEventControllerHandler private val backgroundHandler: Handler private val avatarRenderer: AvatarRenderer,
@TimelineEventControllerHandler
private val backgroundHandler: Handler
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener { ) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener {


interface Callback : ReactionPillCallback, AvatarCallback, BaseCallback { interface Callback : ReactionPillCallback, AvatarCallback, BaseCallback {
@ -188,8 +179,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
// Should be build if not cached or if cached but contains mergedHeader or formattedDay // Should be build if not cached or if cached but contains mergedHeader or formattedDay
// We then are sure we always have items up to date. // We then are sure we always have items up to date.
if (modelCache[position] == null if (modelCache[position] == null
|| modelCache[position]?.mergedHeaderModel != null || modelCache[position]?.mergedHeaderModel != null
|| modelCache[position]?.formattedDayModel != null) { || modelCache[position]?.formattedDayModel != null) {
modelCache[position] = buildItemModels(position, currentSnapshot) modelCache[position] = buildItemModels(position, currentSnapshot)
} }
} }
@ -261,7 +252,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
// => handle case where paginating from mergeable events and we get more // => handle case where paginating from mergeable events and we get more
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull() val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey) val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey)
?: true ?: true
val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState } val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState }
if (isCollapsed) { if (isCollapsed) {
collapsedEventIds.addAll(mergedEventIds) collapsedEventIds.addAll(mergedEventIds)
@ -269,7 +260,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
collapsedEventIds.removeAll(mergedEventIds) collapsedEventIds.removeAll(mergedEventIds)
} }
val mergeId = mergedEventIds.joinToString(separator = "_") { it } val mergeId = mergedEventIds.joinToString(separator = "_") { it }
MergedHeaderItem(isCollapsed, mergeId, mergedData) { MergedHeaderItem(isCollapsed, mergeId, mergedData, avatarRenderer) {
mergeItemCollapseStates[event.localId] = it mergeItemCollapseStates[event.localId] = it
requestModelBuild() requestModelBuild()
} }

View File

@ -46,6 +46,7 @@ import javax.inject.Inject
class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment() { class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment() {


@Inject lateinit var messageActionViewModelFactory: MessageActionsViewModel.Factory @Inject lateinit var messageActionViewModelFactory: MessageActionsViewModel.Factory
@Inject lateinit var avatarRenderer: AvatarRenderer
private val viewModel: MessageActionsViewModel by fragmentViewModel(MessageActionsViewModel::class) private val viewModel: MessageActionsViewModel by fragmentViewModel(MessageActionsViewModel::class)


private lateinit var actionHandlerModel: ActionsHandler private lateinit var actionHandlerModel: ActionsHandler
@ -127,7 +128,7 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
senderNameTextView.text = it.senderName senderNameTextView.text = it.senderName
messageBodyTextView.text = it.messageBody messageBodyTextView.text = it.messageBody
messageTimestampText.text = it.ts messageTimestampText.text = it.ts
AvatarRenderer.render(it.senderAvatarPath, it.userId, it.senderName, senderAvatarImageView) avatarRenderer.render(it.senderAvatarPath, it.userId, it.senderName, senderAvatarImageView)
} else { } else {
bottom_sheet_message_preview.isVisible = false bottom_sheet_message_preview.isVisible = false
} }

View File

@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
@ -33,7 +34,8 @@ import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem
import javax.inject.Inject import javax.inject.Inject


// This class handles timeline event who haven't been successfully decrypted // This class handles timeline event who haven't been successfully decrypted
class EncryptedItemFactory @Inject constructor(private val stringProvider: StringProvider) { class EncryptedItemFactory @Inject constructor(private val stringProvider: StringProvider,
private val avatarRenderer: AvatarRenderer) {


fun create(timelineEvent: TimelineEvent): VectorEpoxyModel<*>? { fun create(timelineEvent: TimelineEvent): VectorEpoxyModel<*>? {
return when { return when {
@ -59,6 +61,7 @@ class EncryptedItemFactory @Inject constructor(private val stringProvider: Strin
showInformation = false showInformation = false
) )
return NoticeItem_() return NoticeItem_()
.avatarRenderer(avatarRenderer)
.noticeText(spannableStr) .noticeText(spannableStr)
.informationData(informationData) .informationData(informationData)
} }

View File

@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
@ -30,7 +31,8 @@ import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_
import javax.inject.Inject import javax.inject.Inject


class EncryptionItemFactory @Inject constructor(private val stringProvider: StringProvider) { class EncryptionItemFactory @Inject constructor(private val stringProvider: StringProvider,
private val avatarRenderer: AvatarRenderer) {


fun create(event: TimelineEvent): NoticeItem? { fun create(event: TimelineEvent): NoticeItem? {
val text = buildNoticeText(event.root, event.senderName) ?: return null val text = buildNoticeText(event.root, event.senderName) ?: return null
@ -43,6 +45,7 @@ class EncryptionItemFactory @Inject constructor(private val stringProvider: Stri
showInformation = false showInformation = false
) )
return NoticeItem_() return NoticeItem_()
.avatarRenderer(avatarRenderer)
.noticeText(text) .noticeText(text)
.informationData(informationData) .informationData(informationData)
} }

View File

@ -29,14 +29,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
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.MessageAudioContent import im.vector.matrix.android.api.session.room.model.message.*
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.riotredesign.EmojiCompatFontProvider import im.vector.riotredesign.EmojiCompatFontProvider
@ -47,23 +40,13 @@ import im.vector.riotredesign.core.linkify.VectorLinkify
import im.vector.riotredesign.core.resources.ColorProvider import im.vector.riotredesign.core.resources.ColorProvider
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.core.utils.DebouncedClickListener import im.vector.riotredesign.core.utils.DebouncedClickListener
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.getColorFromUserId import im.vector.riotredesign.features.home.getColorFromUserId
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotredesign.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotredesign.features.home.room.detail.timeline.item.BlankItem_ import im.vector.riotredesign.features.home.room.detail.timeline.item.*
import im.vector.riotredesign.features.home.room.detail.timeline.item.DefaultItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.DefaultItem_
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageFileItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageFileItem_
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageImageVideoItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageImageVideoItem_
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageTextItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageTextItem_
import im.vector.riotredesign.features.home.room.detail.timeline.item.ReactionInfoData
import im.vector.riotredesign.features.home.room.detail.timeline.item.RedactedMessageItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.RedactedMessageItem_
import im.vector.riotredesign.features.html.EventHtmlRenderer import im.vector.riotredesign.features.html.EventHtmlRenderer
import im.vector.riotredesign.features.media.ImageContentRenderer import im.vector.riotredesign.features.media.ImageContentRenderer
import im.vector.riotredesign.features.media.VideoContentRenderer import im.vector.riotredesign.features.media.VideoContentRenderer
@ -71,12 +54,15 @@ import me.gujun.android.span.span
import javax.inject.Inject import javax.inject.Inject


class MessageItemFactory @Inject constructor( class MessageItemFactory @Inject constructor(
private val avatarRenderer: AvatarRenderer,
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
private val timelineDateFormatter: TimelineDateFormatter, private val timelineDateFormatter: TimelineDateFormatter,
private val htmlRenderer: EventHtmlRenderer, private val htmlRenderer: EventHtmlRenderer,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val emojiCompatFontProvider: EmojiCompatFontProvider) { private val emojiCompatFontProvider: EmojiCompatFontProvider,
private val imageContentRenderer: ImageContentRenderer,
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder) {


fun create(event: TimelineEvent, fun create(event: TimelineEvent,
nextEvent: TimelineEvent?, nextEvent: TimelineEvent?,
@ -89,33 +75,33 @@ class MessageItemFactory @Inject constructor(
val nextDate = nextEvent?.root?.localDateTime() val nextDate = nextEvent?.root?.localDateTime()
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60)) val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
?: false ?: false


val showInformation = addDaySeparator val showInformation = addDaySeparator
|| event.senderAvatar != nextEvent?.senderAvatar || event.senderAvatar != nextEvent?.senderAvatar
|| event.senderName != nextEvent?.senderName || event.senderName != nextEvent?.senderName
|| nextEvent?.root?.getClearType() != EventType.MESSAGE || nextEvent?.root?.getClearType() != EventType.MESSAGE
|| isNextMessageReceivedMoreThanOneHourAgo || isNextMessageReceivedMoreThanOneHourAgo


val time = timelineDateFormatter.formatMessageHour(date) val time = timelineDateFormatter.formatMessageHour(date)
val avatarUrl = event.senderAvatar val avatarUrl = event.senderAvatar
val memberName = event.senderName ?: event.root.sender ?: "" val memberName = event.senderName ?: event.root.sender ?: ""
val formattedMemberName = span(memberName) { val formattedMemberName = span(memberName) {
textColor = colorProvider.getColor(getColorFromUserId(event.root.sender textColor = colorProvider.getColor(getColorFromUserId(event.root.sender
?: "")) ?: ""))
} }
val hasBeenEdited = event.annotations?.editSummary != null val hasBeenEdited = event.annotations?.editSummary != null
val informationData = MessageInformationData(eventId = eventId, val informationData = MessageInformationData(eventId = eventId,
senderId = event.root.sender ?: "", senderId = event.root.sender ?: "",
sendState = event.sendState, sendState = event.sendState,
time = time, time = time,
avatarUrl = avatarUrl, avatarUrl = avatarUrl,
memberName = formattedMemberName, memberName = formattedMemberName,
showInformation = showInformation, showInformation = showInformation,
orderedReactionList = event.annotations?.reactionsSummary?.map { orderedReactionList = event.annotations?.reactionsSummary?.map {
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty()) ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
}, },
hasBeenEdited = hasBeenEdited hasBeenEdited = hasBeenEdited
) )


if (event.root.unsignedData?.redactedEvent != null) { if (event.root.unsignedData?.redactedEvent != null) {
@ -125,9 +111,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
@ -137,16 +123,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,
hasBeenEdited, hasBeenEdited,
event.annotations?.editSummary, event.annotations?.editSummary,
callback) callback)
is MessageTextContent -> buildTextMessageItem(event.sendState, is MessageTextContent -> buildTextMessageItem(event.sendState,
messageContent, messageContent,
informationData, informationData,
hasBeenEdited, hasBeenEdited,
event.annotations?.editSummary, event.annotations?.editSummary,
callback callback
) )
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback) is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback)
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback) is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback)
@ -161,6 +147,7 @@ class MessageItemFactory @Inject constructor(
informationData: MessageInformationData, informationData: MessageInformationData,
callback: TimelineEventController.Callback?): MessageFileItem? { callback: TimelineEventController.Callback?): MessageFileItem? {
return MessageFileItem_() return MessageFileItem_()
.avatarRenderer(avatarRenderer)
.informationData(informationData) .informationData(informationData)
.avatarCallback(callback) .avatarCallback(callback)
.filename(messageContent.body) .filename(messageContent.body)
@ -177,7 +164,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
} }
} }


@ -185,6 +172,7 @@ class MessageItemFactory @Inject constructor(
informationData: MessageInformationData, informationData: MessageInformationData,
callback: TimelineEventController.Callback?): MessageFileItem? { callback: TimelineEventController.Callback?): MessageFileItem? {
return MessageFileItem_() return MessageFileItem_()
.avatarRenderer(avatarRenderer)
.informationData(informationData) .informationData(informationData)
.avatarCallback(callback) .avatarCallback(callback)
.filename(messageContent.body) .filename(messageContent.body)
@ -197,7 +185,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 { _ ->
@ -226,6 +214,9 @@ class MessageItemFactory @Inject constructor(
rotation = messageContent.info?.rotation rotation = messageContent.info?.rotation
) )
return MessageImageVideoItem_() return MessageImageVideoItem_()
.avatarRenderer(avatarRenderer)
.imageContentRenderer(imageContentRenderer)
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
.playable(messageContent.info?.mimeType == "image/gif") .playable(messageContent.info?.mimeType == "image/gif")
.informationData(informationData) .informationData(informationData)
.avatarCallback(callback) .avatarCallback(callback)
@ -242,7 +233,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
} }
} }


@ -267,6 +258,9 @@ class MessageItemFactory @Inject constructor(
) )


return MessageImageVideoItem_() return MessageImageVideoItem_()
.imageContentRenderer(imageContentRenderer)
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
.avatarRenderer(avatarRenderer)
.playable(true) .playable(true)
.informationData(informationData) .informationData(informationData)
.avatarCallback(callback) .avatarCallback(callback)
@ -280,7 +274,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
} }
} }


@ -306,6 +300,7 @@ class MessageItemFactory @Inject constructor(
message(linkifiedBody) message(linkifiedBody)
} }
} }
.avatarRenderer(avatarRenderer)
.informationData(informationData) .informationData(informationData)
.avatarCallback(callback) .avatarCallback(callback)
.reactionPillCallback(callback) .reactionPillCallback(callback)
@ -317,7 +312,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
} }
} }


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


@ -368,6 +363,7 @@ class MessageItemFactory @Inject constructor(
linkifyBody(formattedBody, callback) linkifyBody(formattedBody, callback)
} }
return MessageTextItem_() return MessageTextItem_()
.avatarRenderer(avatarRenderer)
.message(message) .message(message)
.informationData(informationData) .informationData(informationData)
.avatarCallback(callback) .avatarCallback(callback)
@ -383,7 +379,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
} }
} }


@ -406,6 +402,7 @@ class MessageItemFactory @Inject constructor(
message(message) message(message)
} }
} }
.avatarRenderer(avatarRenderer)
.informationData(informationData) .informationData(informationData)
.avatarCallback(callback) .avatarCallback(callback)
.reactionPillCallback(callback) .reactionPillCallback(callback)
@ -416,13 +413,14 @@ class MessageItemFactory @Inject constructor(
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }


private fun buildRedactedItem(informationData: MessageInformationData, private fun buildRedactedItem(informationData: MessageInformationData,
callback: TimelineEventController.Callback?): RedactedMessageItem? { callback: TimelineEventController.Callback?): RedactedMessageItem? {
return RedactedMessageItem_() return RedactedMessageItem_()
.avatarRenderer(avatarRenderer)
.informationData(informationData) .informationData(informationData)
.avatarCallback(callback) .avatarCallback(callback)
} }

View File

@ -17,6 +17,7 @@
package im.vector.riotredesign.features.home.room.detail.timeline.factory package im.vector.riotredesign.features.home.room.detail.timeline.factory


import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar
@ -26,7 +27,8 @@ import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_
import javax.inject.Inject import javax.inject.Inject


class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEventFormatter) { class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEventFormatter,
private val avatarRenderer: AvatarRenderer) {


fun create(event: TimelineEvent, fun create(event: TimelineEvent,
callback: TimelineEventController.Callback?): NoticeItem? { callback: TimelineEventController.Callback?): NoticeItem? {
@ -41,6 +43,7 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv
) )


return NoticeItem_() return NoticeItem_()
.avatarRenderer(avatarRenderer)
.noticeText(formattedText) .noticeText(formattedText)
.informationData(informationData) .informationData(informationData)
.baseCallback(callback) .baseCallback(callback)

View File

@ -25,10 +25,12 @@ import android.widget.TextView
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.features.media.ImageContentRenderer import im.vector.riotredesign.features.media.ImageContentRenderer
import java.io.File import java.io.File
import javax.inject.Inject


object ContentUploadStateTrackerBinder { class ContentUploadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder) {


private val updateListeners = mutableMapOf<String, ContentUploadStateTracker.UpdateListener>() private val updateListeners = mutableMapOf<String, ContentUploadStateTracker.UpdateListener>()


@ -36,7 +38,7 @@ object ContentUploadStateTrackerBinder {
mediaData: ImageContentRenderer.Data, mediaData: ImageContentRenderer.Data,
progressLayout: ViewGroup) { progressLayout: ViewGroup) {


Matrix.getInstance(progressLayout.context).currentSession?.also { session -> activeSessionHolder.getActiveSession().also { session ->
val uploadStateTracker = session.contentUploadProgressTracker() val uploadStateTracker = session.contentUploadProgressTracker()
val updateListener = ContentMediaProgressUpdater(progressLayout, mediaData) val updateListener = ContentMediaProgressUpdater(progressLayout, mediaData)
updateListeners[eventId] = updateListener updateListeners[eventId] = updateListener
@ -44,8 +46,8 @@ object ContentUploadStateTrackerBinder {
} }
} }


fun unbind(eventId: String, progressLayout: ViewGroup) { fun unbind(eventId: String) {
Matrix.getInstance(progressLayout.context).currentSession?.also { session -> activeSessionHolder.getActiveSession().also { session ->
val uploadStateTracker = session.contentUploadProgressTracker() val uploadStateTracker = session.contentUploadProgressTracker()
updateListeners[eventId]?.also { updateListeners[eventId]?.also {
uploadStateTracker.untrack(eventId, it) uploadStateTracker.untrack(eventId, it)

View File

@ -40,6 +40,8 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {


abstract val informationData: MessageInformationData abstract val informationData: MessageInformationData


abstract val avatarRenderer: AvatarRenderer

@EpoxyAttribute @EpoxyAttribute
var longClickListener: View.OnLongClickListener? = null var longClickListener: View.OnLongClickListener? = null


@ -96,7 +98,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
holder.timeView.visibility = View.VISIBLE holder.timeView.visibility = View.VISIBLE
holder.timeView.text = informationData.time holder.timeView.text = informationData.time
holder.memberNameView.text = informationData.memberName holder.memberNameView.text = informationData.memberName
AvatarRenderer.render(informationData.avatarUrl, informationData.senderId, informationData.memberName?.toString(), holder.avatarImageView) avatarRenderer.render(informationData.avatarUrl, informationData.senderId, informationData.memberName?.toString(), holder.avatarImageView)
holder.view.setOnClickListener(cellClickListener) holder.view.setOnClickListener(cellClickListener)
holder.view.setOnLongClickListener(longClickListener) holder.view.setOnLongClickListener(longClickListener)
holder.avatarImageView.setOnLongClickListener(longClickListener) holder.avatarImageView.setOnLongClickListener(longClickListener)

View File

@ -30,6 +30,7 @@ import im.vector.riotredesign.features.home.AvatarRenderer
data class MergedHeaderItem(private val isCollapsed: Boolean, data class MergedHeaderItem(private val isCollapsed: Boolean,
private val mergeId: String, private val mergeId: String,
private val mergeData: List<Data>, private val mergeData: List<Data>,
private val avatarRenderer: AvatarRenderer,
private val onCollapsedStateChanged: (Boolean) -> Unit private val onCollapsedStateChanged: (Boolean) -> Unit
) : BaseEventItem<MergedHeaderItem.Holder>() { ) : BaseEventItem<MergedHeaderItem.Holder>() {


@ -63,7 +64,7 @@ data class MergedHeaderItem(private val isCollapsed: Boolean,
val data = distinctMergeData.getOrNull(index) val data = distinctMergeData.getOrNull(index)
if (data != null && view is ImageView) { if (data != null && view is ImageView) {
view.visibility = View.VISIBLE view.visibility = View.VISIBLE
AvatarRenderer.render(data.avatarUrl, data.userId, data.memberName, view) avatarRenderer.render(data.avatarUrl, data.userId, data.memberName, view)
} else { } else {
view.visibility = View.GONE view.visibility = View.GONE
} }

View File

@ -25,6 +25,7 @@ import androidx.annotation.DrawableRes
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.features.home.AvatarRenderer


@EpoxyModelClass(layout = R.layout.item_timeline_event_base) @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() { abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
@ -37,6 +38,8 @@ abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
@EpoxyAttribute @EpoxyAttribute
override lateinit var informationData: MessageInformationData override lateinit var informationData: MessageInformationData
@EpoxyAttribute @EpoxyAttribute
override lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
var clickListener: View.OnClickListener? = null var clickListener: View.OnClickListener? = null


override fun bind(holder: Holder) { override fun bind(holder: Holder) {

View File

@ -22,6 +22,7 @@ import android.widget.ImageView
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.riotredesign.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.riotredesign.features.media.ImageContentRenderer import im.vector.riotredesign.features.media.ImageContentRenderer


@ -33,14 +34,20 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
@EpoxyAttribute @EpoxyAttribute
override lateinit var informationData: MessageInformationData override lateinit var informationData: MessageInformationData
@EpoxyAttribute @EpoxyAttribute
override lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
var playable: Boolean = false var playable: Boolean = false
@EpoxyAttribute @EpoxyAttribute
var clickListener: View.OnClickListener? = null var clickListener: View.OnClickListener? = null
@EpoxyAttribute
lateinit var imageContentRenderer: ImageContentRenderer
@EpoxyAttribute
lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder


override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
ImageContentRenderer.render(mediaData, ImageContentRenderer.Mode.THUMBNAIL, holder.imageView) imageContentRenderer.render(mediaData, ImageContentRenderer.Mode.THUMBNAIL, holder.imageView)
ContentUploadStateTrackerBinder.bind(informationData.eventId, mediaData, holder.progressLayout) contentUploadStateTrackerBinder.bind(informationData.eventId, mediaData, holder.progressLayout)
holder.imageView.setOnClickListener(clickListener) holder.imageView.setOnClickListener(clickListener)
holder.imageView.setOnLongClickListener(longClickListener) holder.imageView.setOnLongClickListener(longClickListener)
holder.mediaContentView.setOnClickListener(cellClickListener) holder.mediaContentView.setOnClickListener(cellClickListener)
@ -50,7 +57,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
} }


override fun unbind(holder: Holder) { override fun unbind(holder: Holder) {
ContentUploadStateTrackerBinder.unbind(informationData.eventId, holder.progressLayout) contentUploadStateTrackerBinder.unbind(informationData.eventId)
super.unbind(holder) super.unbind(holder)
} }



View File

@ -24,6 +24,7 @@ import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.utils.containsOnlyEmojis import im.vector.riotredesign.core.utils.containsOnlyEmojis
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.html.PillImageSpan import im.vector.riotredesign.features.html.PillImageSpan
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -37,6 +38,8 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
@EpoxyAttribute @EpoxyAttribute
var message: CharSequence? = null var message: CharSequence? = null
@EpoxyAttribute @EpoxyAttribute
override lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
override lateinit var informationData: MessageInformationData override lateinit var informationData: MessageInformationData


val mvmtMethod = BetterLinkMovementMethod.newInstance().also { val mvmtMethod = BetterLinkMovementMethod.newInstance().also {

View File

@ -28,6 +28,9 @@ import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventCo
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo) @EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() { abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {


@EpoxyAttribute
lateinit var avatarRenderer: AvatarRenderer

@EpoxyAttribute @EpoxyAttribute
var noticeText: CharSequence? = null var noticeText: CharSequence? = null


@ -46,7 +49,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
holder.noticeTextView.text = noticeText holder.noticeTextView.text = noticeText
AvatarRenderer.render( avatarRenderer.render(
informationData.avatarUrl, informationData.avatarUrl,
informationData.senderId, informationData.senderId,
informationData.memberName?.toString() informationData.memberName?.toString()

View File

@ -3,12 +3,15 @@ package im.vector.riotredesign.features.home.room.detail.timeline.item
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.features.home.AvatarRenderer


@EpoxyModelClass(layout = R.layout.item_timeline_event_base) @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class RedactedMessageItem : AbsMessageItem<RedactedMessageItem.Holder>() { abstract class RedactedMessageItem : AbsMessageItem<RedactedMessageItem.Holder>() {


@EpoxyAttribute @EpoxyAttribute
override lateinit var informationData: MessageInformationData override lateinit var informationData: MessageInformationData
@EpoxyAttribute
override lateinit var avatarRenderer: AvatarRenderer


override fun getStubType(): Int = STUB_ID override fun getStubType(): Int = STUB_ID



View File

@ -25,13 +25,15 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.core.resources.DateProvider import im.vector.riotredesign.core.resources.DateProvider
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
import javax.inject.Inject import javax.inject.Inject


class RoomSummaryController @Inject constructor(private val stringProvider: StringProvider, class RoomSummaryController @Inject constructor(private val stringProvider: StringProvider,
private val eventFormatter: NoticeEventFormatter, private val eventFormatter: NoticeEventFormatter,
private val timelineDateFormatter: TimelineDateFormatter private val timelineDateFormatter: TimelineDateFormatter,
private val avatarRenderer: AvatarRenderer
) : TypedEpoxyController<RoomListViewState>() { ) : TypedEpoxyController<RoomListViewState>() {


var callback: Callback? = null var callback: Callback? = null
@ -110,6 +112,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri


} }
roomSummaryItem { roomSummaryItem {
avatarRenderer(avatarRenderer)
id(roomSummary.roomId) id(roomSummary.roomId)
roomId(roomSummary.roomId) roomId(roomSummary.roomId)
lastEventTime(lastMessageTime) lastEventTime(lastMessageTime)

View File

@ -30,6 +30,7 @@ import im.vector.riotredesign.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_room) @EpoxyModelClass(layout = R.layout.item_room)
abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() { abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {


@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var roomName: CharSequence @EpoxyAttribute lateinit var roomName: CharSequence
@EpoxyAttribute lateinit var roomId: String @EpoxyAttribute lateinit var roomId: String
@EpoxyAttribute lateinit var lastFormattedEvent: CharSequence @EpoxyAttribute lateinit var lastFormattedEvent: CharSequence
@ -47,7 +48,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
holder.lastEventTimeView.text = lastEventTime holder.lastEventTimeView.text = lastEventTime
holder.lastEventView.text = lastFormattedEvent holder.lastEventView.text = lastFormattedEvent
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadCount, showHighlighted)) holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadCount, showHighlighted))
AvatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView) avatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView)
} }


class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {

View File

@ -26,37 +26,25 @@ import im.vector.matrix.android.api.permalinks.PermalinkParser
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.glide.GlideRequests import im.vector.riotredesign.core.glide.GlideRequests
import im.vector.riotredesign.features.home.AvatarRenderer
import org.commonmark.node.BlockQuote import org.commonmark.node.BlockQuote
import org.commonmark.node.HtmlBlock import org.commonmark.node.HtmlBlock
import org.commonmark.node.HtmlInline import org.commonmark.node.HtmlInline
import org.commonmark.node.Node import org.commonmark.node.Node
import ru.noties.markwon.AbstractMarkwonPlugin import ru.noties.markwon.*
import ru.noties.markwon.Markwon
import ru.noties.markwon.MarkwonConfiguration
import ru.noties.markwon.MarkwonVisitor
import ru.noties.markwon.SpannableBuilder
import ru.noties.markwon.html.HtmlTag import ru.noties.markwon.html.HtmlTag
import ru.noties.markwon.html.MarkwonHtmlParserImpl import ru.noties.markwon.html.MarkwonHtmlParserImpl
import ru.noties.markwon.html.MarkwonHtmlRenderer import ru.noties.markwon.html.MarkwonHtmlRenderer
import ru.noties.markwon.html.TagHandler import ru.noties.markwon.html.TagHandler
import ru.noties.markwon.html.tag.BlockquoteHandler import ru.noties.markwon.html.tag.*
import ru.noties.markwon.html.tag.EmphasisHandler
import ru.noties.markwon.html.tag.HeadingHandler
import ru.noties.markwon.html.tag.ImageHandler
import ru.noties.markwon.html.tag.LinkHandler
import ru.noties.markwon.html.tag.ListHandler
import ru.noties.markwon.html.tag.StrikeHandler
import ru.noties.markwon.html.tag.StrongEmphasisHandler
import ru.noties.markwon.html.tag.SubScriptHandler
import ru.noties.markwon.html.tag.SuperScriptHandler
import ru.noties.markwon.html.tag.UnderlineHandler
import java.util.Arrays.asList import java.util.Arrays.asList
import javax.inject.Inject import javax.inject.Inject


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


fun render(text: String): CharSequence { fun render(text: String): CharSequence {
@ -67,6 +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 session: Session) : AbstractMarkwonPlugin() { private val session: Session) : AbstractMarkwonPlugin() {


override fun configureConfiguration(builder: MarkwonConfiguration.Builder) { override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
@ -80,7 +69,7 @@ private class MatrixPlugin private constructor(private val glideRequests: GlideR
ImageHandler.create()) ImageHandler.create())
.setHandler( .setHandler(
"a", "a",
MxLinkHandler(glideRequests, context, session)) MxLinkHandler(glideRequests, context, avatarRenderer, session))
.setHandler( .setHandler(
"blockquote", "blockquote",
BlockquoteHandler()) BlockquoteHandler())
@ -112,7 +101,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())


} }


@ -135,14 +124,15 @@ private class MatrixPlugin private constructor(private val glideRequests: GlideR


companion object { companion object {


fun create(glideRequests: GlideRequests, context: Context, session: Session): MatrixPlugin { fun create(glideRequests: GlideRequests, context: Context, avatarRenderer: AvatarRenderer, session: Session): MatrixPlugin {
return MatrixPlugin(glideRequests, context, session) return MatrixPlugin(glideRequests, context, avatarRenderer, session)
} }
} }
} }


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 session: Session) : TagHandler() { private val session: Session) : TagHandler() {


private val linkHandler = LinkHandler() private val linkHandler = LinkHandler()
@ -154,7 +144,7 @@ private class MxLinkHandler(private val glideRequests: GlideRequests,
when (permalinkData) { when (permalinkData) {
is PermalinkData.UserLink -> { is PermalinkData.UserLink -> {
val user = session.getUser(permalinkData.userId) val user = session.getUser(permalinkData.userId)
val span = PillImageSpan(glideRequests, context, permalinkData.userId, user) val span = PillImageSpan(glideRequests, avatarRenderer, context, permalinkData.userId, user)
SpannableBuilder.setSpans( SpannableBuilder.setSpans(
visitor.builder(), visitor.builder(),
span, span,

View File

@ -37,6 +37,7 @@ import java.lang.ref.WeakReference
* It's needed to call [bind] method to start requesting avatar, otherwise only the placeholder icon will be displayed if not already cached. * It's needed to call [bind] method to start requesting avatar, otherwise only the placeholder icon will be displayed if not already cached.
*/ */
class PillImageSpan(private val glideRequests: GlideRequests, class PillImageSpan(private val glideRequests: GlideRequests,
private val avatarRenderer: AvatarRenderer,
private val context: Context, private val context: Context,
private val userId: String, private val userId: String,
private val user: User?) : ReplacementSpan() { private val user: User?) : ReplacementSpan() {
@ -52,7 +53,7 @@ class PillImageSpan(private val glideRequests: GlideRequests,
@UiThread @UiThread
fun bind(textView: TextView) { fun bind(textView: TextView) {
tv = WeakReference(textView) tv = WeakReference(textView)
AvatarRenderer.render(context, glideRequests, user?.avatarUrl, userId, displayName, target) avatarRenderer.render(context, glideRequests, user?.avatarUrl, userId, displayName, target)
} }


// ReplacementSpan ***************************************************************************** // ReplacementSpan *****************************************************************************
@ -105,7 +106,7 @@ class PillImageSpan(private val glideRequests: GlideRequests,
textStartPadding = textPadding textStartPadding = textPadding
setChipMinHeightResource(R.dimen.pill_min_height) setChipMinHeightResource(R.dimen.pill_min_height)
setChipIconSizeResource(R.dimen.pill_avatar_size) setChipIconSizeResource(R.dimen.pill_avatar_size)
chipIcon = AvatarRenderer.getPlaceholderDrawable(context, userId, displayName) chipIcon = avatarRenderer.getPlaceholderDrawable(context, userId, displayName)
setBounds(0, 0, intrinsicWidth, intrinsicHeight) setBounds(0, 0, intrinsicWidth, intrinsicHeight)
} }
} }

View File

@ -24,8 +24,10 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.HasScreenInjector
import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.AvatarRenderer
import kotlinx.android.synthetic.main.vector_invite_view.view.* import kotlinx.android.synthetic.main.vector_invite_view.view.*
import javax.inject.Inject


class VectorInviteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) class VectorInviteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
: ConstraintLayout(context, attrs, defStyle) { : ConstraintLayout(context, attrs, defStyle) {
@ -40,9 +42,13 @@ class VectorInviteView @JvmOverloads constructor(context: Context, attrs: Attrib
SMALL SMALL
} }


@Inject lateinit var avatarRenderer: AvatarRenderer
var callback: Callback? = null var callback: Callback? = null


init { init {
if(context is HasScreenInjector){
context.injector().inject(this)
}
View.inflate(context, R.layout.vector_invite_view, this) View.inflate(context, R.layout.vector_invite_view, this)
setBackgroundColor(Color.WHITE) setBackgroundColor(Color.WHITE)
inviteRejectView.setOnClickListener { callback?.onRejectInvite() } inviteRejectView.setOnClickListener { callback?.onRejectInvite() }
@ -52,7 +58,7 @@ class VectorInviteView @JvmOverloads constructor(context: Context, attrs: Attrib
fun render(sender: User, mode: Mode = Mode.LARGE) { fun render(sender: User, mode: Mode = Mode.LARGE) {
if (mode == Mode.LARGE) { if (mode == Mode.LARGE) {
updateLayoutParams { height = LayoutParams.MATCH_CONSTRAINT } updateLayoutParams { height = LayoutParams.MATCH_CONSTRAINT }
AvatarRenderer.render(sender.avatarUrl, sender.userId, sender.displayName, inviteAvatarView) avatarRenderer.render(sender.avatarUrl, sender.userId, sender.displayName, inviteAvatarView)
inviteIdentifierView.text = sender.userId inviteIdentifierView.text = sender.userId
inviteNameView.text = sender.displayName inviteNameView.text = sender.displayName
inviteLabelView.text = context.getString(R.string.send_you_invite) inviteLabelView.text = context.getString(R.string.send_you_invite)

View File

@ -30,7 +30,9 @@ import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.di.ScreenComponent import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.extensions.openAndStartSync
import im.vector.riotredesign.core.extensions.showPassword import im.vector.riotredesign.core.extensions.showPassword
import im.vector.riotredesign.core.platform.VectorBaseActivity import im.vector.riotredesign.core.platform.VectorBaseActivity
import im.vector.riotredesign.features.home.HomeActivity import im.vector.riotredesign.features.home.HomeActivity
@ -47,6 +49,7 @@ private const val DEFAULT_ANTIVIRUS_SERVER_URI = "https://matrix.org"
class LoginActivity : VectorBaseActivity() { class LoginActivity : VectorBaseActivity() {


@Inject lateinit var authenticator: Authenticator @Inject lateinit var authenticator: Authenticator
@Inject lateinit var activeSessionHolder: ActiveSessionHolder


private var passwordShown = false private var passwordShown = false


@ -78,11 +81,8 @@ class LoginActivity : VectorBaseActivity() {
progressBar.visibility = View.VISIBLE progressBar.visibility = View.VISIBLE
authenticator.authenticate(homeServerConnectionConfig, login, password, object : MatrixCallback<Session> { authenticator.authenticate(homeServerConnectionConfig, login, password, object : MatrixCallback<Session> {
override fun onSuccess(data: Session) { override fun onSuccess(data: Session) {
Matrix.getInstance(this@LoginActivity).currentSession = data activeSessionHolder.setActiveSession(data)
data.open() data.openAndStartSync()
data.setFilter(FilterService.FilterPreset.RiotFilter)
data.startSync()

goToHome() goToHome()
} }



View File

@ -24,12 +24,14 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.github.piasy.biv.view.BigImageView import com.github.piasy.biv.view.BigImageView
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.utils.DimensionUtils.dpToPx import im.vector.riotredesign.core.utils.DimensionUtils.dpToPx
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import java.io.File import java.io.File
import javax.inject.Inject


object ImageContentRenderer { class ImageContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder) {


@Parcelize @Parcelize
data class Data( data class Data(
@ -57,26 +59,26 @@ object ImageContentRenderer {
val (width, height) = processSize(data, mode) val (width, height) = processSize(data, mode)
imageView.layoutParams.height = height imageView.layoutParams.height = height
imageView.layoutParams.width = width imageView.layoutParams.width = width
val contentUrlResolver = Matrix.getInstance(imageView.context).currentSession!!.contentUrlResolver() val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
val resolvedUrl = when (mode) { val resolvedUrl = when (mode) {
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url) Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE) Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
} }
//Fallback to base url //Fallback to base url
?: data.url ?: data.url


GlideApp GlideApp
.with(imageView) .with(imageView)
.load(resolvedUrl) .load(resolvedUrl)
.dontAnimate() .dontAnimate()
.transform(RoundedCorners(dpToPx(8,imageView.context))) .transform(RoundedCorners(dpToPx(8, imageView.context)))
.thumbnail(0.3f) .thumbnail(0.3f)
.into(imageView) .into(imageView)
} }


fun render(data: Data, imageView: BigImageView) { fun render(data: Data, imageView: BigImageView) {
val (width, height) = processSize(data, Mode.THUMBNAIL) val (width, height) = processSize(data, Mode.THUMBNAIL)
val contentUrlResolver = Matrix.getInstance(imageView.context).currentSession!!.contentUrlResolver() val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
val fullSize = contentUrlResolver.resolveFullSize(data.url) val fullSize = contentUrlResolver.resolveFullSize(data.url)
val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE) val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
imageView.showImage( imageView.showImage(

View File

@ -24,12 +24,20 @@ import android.os.Bundle
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import com.github.piasy.biv.indicator.progresspie.ProgressPieIndicator import com.github.piasy.biv.indicator.progresspie.ProgressPieIndicator
import com.github.piasy.biv.view.GlideImageViewFactory import com.github.piasy.biv.view.GlideImageViewFactory
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.platform.VectorBaseActivity import im.vector.riotredesign.core.platform.VectorBaseActivity
import kotlinx.android.synthetic.main.activity_image_media_viewer.* import kotlinx.android.synthetic.main.activity_image_media_viewer.*
import javax.inject.Inject




class ImageMediaViewerActivity : VectorBaseActivity() { class ImageMediaViewerActivity : VectorBaseActivity() {


@Inject lateinit var imageContentRenderer: ImageContentRenderer

override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}

override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(im.vector.riotredesign.R.layout.activity_image_media_viewer) setContentView(im.vector.riotredesign.R.layout.activity_image_media_viewer)
@ -40,7 +48,7 @@ class ImageMediaViewerActivity : VectorBaseActivity() {
configureToolbar(imageMediaViewerToolbar, mediaData) configureToolbar(imageMediaViewerToolbar, mediaData)
imageMediaViewerImageView.setImageViewFactory(GlideImageViewFactory()) imageMediaViewerImageView.setImageViewFactory(GlideImageViewFactory())
imageMediaViewerImageView.setProgressIndicator(ProgressPieIndicator()) imageMediaViewerImageView.setProgressIndicator(ProgressPieIndicator())
ImageContentRenderer.render(mediaData, imageMediaViewerImageView) imageContentRenderer.render(mediaData, imageMediaViewerImageView)
} }
} }



View File

@ -20,9 +20,11 @@ import android.os.Parcelable
import android.widget.ImageView import android.widget.ImageView
import android.widget.VideoView import android.widget.VideoView
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.core.di.ActiveSessionHolder
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import javax.inject.Inject


object VideoContentRenderer { class VideoContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder){


@Parcelize @Parcelize
data class Data( data class Data(
@ -32,7 +34,7 @@ object VideoContentRenderer {
) : Parcelable ) : Parcelable


fun render(data: Data, thumbnailView: ImageView, videoView: VideoView) { fun render(data: Data, thumbnailView: ImageView, videoView: VideoView) {
val contentUrlResolver = Matrix.getInstance(videoView.context).currentSession!!.contentUrlResolver() val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
val resolvedUrl = contentUrlResolver.resolveFullSize(data.videoUrl) val resolvedUrl = contentUrlResolver.resolveFullSize(data.videoUrl)
videoView.setVideoPath(resolvedUrl) videoView.setVideoPath(resolvedUrl)
videoView.start() videoView.start()

View File

@ -20,12 +20,21 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.platform.VectorBaseActivity import im.vector.riotredesign.core.platform.VectorBaseActivity
import im.vector.riotredesign.features.home.AvatarRenderer
import kotlinx.android.synthetic.main.activity_video_media_viewer.* import kotlinx.android.synthetic.main.activity_video_media_viewer.*
import javax.inject.Inject




class VideoMediaViewerActivity : VectorBaseActivity() { class VideoMediaViewerActivity : VectorBaseActivity() {


@Inject lateinit var videoContentRenderer: VideoContentRenderer

override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}

override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(im.vector.riotredesign.R.layout.activity_video_media_viewer) setContentView(im.vector.riotredesign.R.layout.activity_video_media_viewer)
@ -34,7 +43,7 @@ class VideoMediaViewerActivity : VectorBaseActivity() {
finish() finish()
} else { } else {
configureToolbar(videoMediaViewerToolbar, mediaData) configureToolbar(videoMediaViewerToolbar, mediaData)
VideoContentRenderer.render(mediaData, videoMediaViewerThumbnailView, videoMediaViewerVideoView) videoContentRenderer.render(mediaData, videoMediaViewerThumbnailView, videoMediaViewerVideoView)
} }
} }



View File

@ -26,9 +26,11 @@ import butterknife.BindView
import butterknife.OnCheckedChanged import butterknife.OnCheckedChanged
import butterknife.OnTextChanged import butterknife.OnTextChanged
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.platform.VectorBaseActivity import im.vector.riotredesign.core.platform.VectorBaseActivity
import kotlinx.android.synthetic.main.activity_bug_report.* import kotlinx.android.synthetic.main.activity_bug_report.*
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject


/** /**
* Form to send a bug report * Form to send a bug report
@ -66,13 +68,17 @@ class BugReportActivity : VectorBaseActivity() {
@BindView(R.id.bug_report_mask_view) @BindView(R.id.bug_report_mask_view)
lateinit var mMaskView: View lateinit var mMaskView: View


override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}

override fun getLayoutRes() = R.layout.activity_bug_report override fun getLayoutRes() = R.layout.activity_bug_report


override fun initUiAndData() { override fun initUiAndData() {
configureToolbar(bugReportToolbar) configureToolbar(bugReportToolbar)


if (BugReporter.screenshot != null) { if (bugReporter.screenshot != null) {
mScreenShotPreview.setImageBitmap(BugReporter.screenshot) mScreenShotPreview.setImageBitmap(bugReporter.screenshot)
} else { } else {
mScreenShotPreview.isVisible = false mScreenShotPreview.isVisible = false
mIncludeScreenShotButton.isChecked = false mIncludeScreenShotButton.isChecked = false
@ -120,7 +126,7 @@ class BugReportActivity : VectorBaseActivity() {
mProgressBar.isVisible = true mProgressBar.isVisible = true
mProgressBar.progress = 0 mProgressBar.progress = 0


BugReporter.sendBugReport(this, bugReporter.sendBugReport(this,
mIncludeLogsButton.isChecked, mIncludeLogsButton.isChecked,
mIncludeCrashLogsButton.isChecked, mIncludeCrashLogsButton.isChecked,
mIncludeScreenShotButton.isChecked, mIncludeScreenShotButton.isChecked,
@ -190,12 +196,12 @@ class BugReportActivity : VectorBaseActivity() {


@OnCheckedChanged(R.id.bug_report_button_include_screenshot) @OnCheckedChanged(R.id.bug_report_button_include_screenshot)
internal fun onSendScreenshotChanged() { internal fun onSendScreenshotChanged() {
mScreenShotPreview.isVisible = mIncludeScreenShotButton.isChecked && BugReporter.screenshot != null mScreenShotPreview.isVisible = mIncludeScreenShotButton.isChecked && bugReporter.screenshot != null
} }


override fun onBackPressed() { override fun onBackPressed() {
// Ensure there is no crash status remaining, which will be sent later on by mistake // Ensure there is no crash status remaining, which will be sent later on by mistake
BugReporter.deleteCrashFile(this) bugReporter.deleteCrashFile(this)


super.onBackPressed() super.onBackPressed()
} }

View File

@ -30,6 +30,7 @@ import android.view.View
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.BuildConfig import im.vector.riotredesign.BuildConfig
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.extensions.toOnOff import im.vector.riotredesign.core.extensions.toOnOff
import im.vector.riotredesign.core.utils.getDeviceLocale import im.vector.riotredesign.core.utils.getDeviceLocale
import im.vector.riotredesign.features.settings.VectorLocale import im.vector.riotredesign.features.settings.VectorLocale
@ -42,19 +43,26 @@ import java.io.*
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.util.* import java.util.*
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
import javax.inject.Inject
import javax.inject.Singleton


/** /**
* BugReporter creates and sends the bug reports. * BugReporter creates and sends the bug reports.
*/ */
object BugReporter { @Singleton
class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSessionHolder){
var inMultiWindowMode = false var inMultiWindowMode = false


// filenames companion object {
private const val LOG_CAT_ERROR_FILENAME = "logcatError.log" // filenames
private const val LOG_CAT_FILENAME = "logcat.log" private const val LOG_CAT_ERROR_FILENAME = "logcatError.log"
private const val LOG_CAT_SCREENSHOT_FILENAME = "screenshot.png" private const val LOG_CAT_FILENAME = "logcat.log"
private const val CRASH_FILENAME = "crash.log" private const val LOG_CAT_SCREENSHOT_FILENAME = "screenshot.png"
private const val CRASH_FILENAME = "crash.log"


private const val BUFFER_SIZE = 1024 * 1024 * 50

}


// the http client // the http client
private val mOkHttpClient = OkHttpClient() private val mOkHttpClient = OkHttpClient()
@ -74,8 +82,6 @@ object BugReporter {
var screenshot: Bitmap? = null var screenshot: Bitmap? = null
private set private set


private const val BUFFER_SIZE = 1024 * 1024 * 50

private val LOGCAT_CMD_ERROR = arrayOf("logcat", ///< Run 'logcat' command private val LOGCAT_CMD_ERROR = arrayOf("logcat", ///< Run 'logcat' command
"-d", ///< Dump the log rather than continue outputting it "-d", ///< Dump the log rather than continue outputting it
"-v", // formatting "-v", // formatting
@ -195,7 +201,7 @@ object BugReporter {
var matrixSdkVersion = "undefined" var matrixSdkVersion = "undefined"
var olmVersion = "undefined" var olmVersion = "undefined"


Matrix.getInstance(context).currentSession?.let { session -> activeSessionHolder.getActiveSession().let { session ->
userId = session.sessionParams.credentials.userId userId = session.sessionParams.credentials.userId
deviceId = session.sessionParams.credentials.deviceId ?: "undefined" deviceId = session.sessionParams.credentials.deviceId ?: "undefined"
// TODO matrixSdkVersion = session.getVersion(true); // TODO matrixSdkVersion = session.getVersion(true);

View File

@ -16,7 +16,6 @@


package im.vector.riotredesign.features.rageshake package im.vector.riotredesign.features.rageshake


import android.app.Activity
import android.content.Context import android.content.Context
import android.hardware.Sensor import android.hardware.Sensor
import android.hardware.SensorManager import android.hardware.SensorManager
@ -26,8 +25,10 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit import androidx.core.content.edit
import com.squareup.seismic.ShakeDetector import com.squareup.seismic.ShakeDetector
import im.vector.riotredesign.R import im.vector.riotredesign.R
import javax.inject.Inject


class RageShake(val activity: Activity) : ShakeDetector.Listener { class RageShake @Inject constructor(private val activity: AppCompatActivity,
private val bugReporter: BugReporter) : ShakeDetector.Listener {


private var shakeDetector: ShakeDetector? = null private var shakeDetector: ShakeDetector? = null


@ -94,7 +95,7 @@ class RageShake(val activity: Activity) : ShakeDetector.Listener {
} }


private fun openBugReportScreen() { private fun openBugReportScreen() {
BugReporter.openBugReportScreen(activity) bugReporter.openBugReportScreen(activity)
} }


companion object { companion object {

View File

@ -16,7 +16,6 @@


package im.vector.riotredesign.features.rageshake package im.vector.riotredesign.features.rageshake


import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import androidx.core.content.edit import androidx.core.content.edit
@ -25,12 +24,16 @@ import im.vector.riotredesign.BuildConfig
import timber.log.Timber import timber.log.Timber
import java.io.PrintWriter import java.io.PrintWriter
import java.io.StringWriter import java.io.StringWriter
import javax.inject.Inject
import javax.inject.Singleton


@SuppressLint("StaticFieldLeak") @Singleton
object VectorUncaughtExceptionHandler : Thread.UncaughtExceptionHandler { class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter: BugReporter) : Thread.UncaughtExceptionHandler {


// key to save the crash status // key to save the crash status
private const val PREFS_CRASH_KEY = "PREFS_CRASH_KEY" companion object {
private const val PREFS_CRASH_KEY = "PREFS_CRASH_KEY"
}


private var vectorVersion: String = "" private var vectorVersion: String = ""
private var matrixSdkVersion: String = "" private var matrixSdkVersion: String = ""
@ -44,9 +47,7 @@ object VectorUncaughtExceptionHandler : Thread.UncaughtExceptionHandler {
*/ */
fun activate(context: Context) { fun activate(context: Context) {
this.context = context this.context = context

previousHandler = Thread.getDefaultUncaughtExceptionHandler() previousHandler = Thread.getDefaultUncaughtExceptionHandler()

Thread.setDefaultUncaughtExceptionHandler(this) Thread.setDefaultUncaughtExceptionHandler(this)
} }


@ -58,15 +59,10 @@ object VectorUncaughtExceptionHandler : Thread.UncaughtExceptionHandler {
* @return the exception description * @return the exception description
*/ */
override fun uncaughtException(thread: Thread, throwable: Throwable) { override fun uncaughtException(thread: Thread, throwable: Throwable) {
if (context == null) { Timber.v("Uncaught exception: $throwable")
previousHandler?.uncaughtException(thread, throwable)
return
}

PreferenceManager.getDefaultSharedPreferences(context).edit { PreferenceManager.getDefaultSharedPreferences(context).edit {
putBoolean(PREFS_CRASH_KEY, true) putBoolean(PREFS_CRASH_KEY, true)
} }

val b = StringBuilder() val b = StringBuilder()
val appName = "RiotX" // TODO Matrix.getApplicationName() val appName = "RiotX" // TODO Matrix.getApplicationName()


@ -114,7 +110,7 @@ object VectorUncaughtExceptionHandler : Thread.UncaughtExceptionHandler {


val bugDescription = b.toString() val bugDescription = b.toString()


BugReporter.saveCrashReport(context, bugDescription) bugReporter.saveCrashReport(context, bugDescription)


// Show the classical system popup // Show the classical system popup
previousHandler?.uncaughtException(thread, throwable) previousHandler?.uncaughtException(thread, throwable)

View File

@ -31,6 +31,9 @@ import im.vector.riotredesign.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_public_room) @EpoxyModelClass(layout = R.layout.item_public_room)
abstract class PublicRoomItem : VectorEpoxyModel<PublicRoomItem.Holder>() { abstract class PublicRoomItem : VectorEpoxyModel<PublicRoomItem.Holder>() {


@EpoxyAttribute
lateinit var avatarRenderer: AvatarRenderer

@EpoxyAttribute @EpoxyAttribute
var avatarUrl: String? = null var avatarUrl: String? = null


@ -61,7 +64,7 @@ abstract class PublicRoomItem : VectorEpoxyModel<PublicRoomItem.Holder>() {
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
holder.rootView.setOnClickListener { globalListener?.invoke() } holder.rootView.setOnClickListener { globalListener?.invoke() }


AvatarRenderer.render(avatarUrl, roomId!!, roomName, holder.avatarView) avatarRenderer.render(avatarUrl, roomId!!, roomName, holder.avatarView)
holder.nameView.text = roomName holder.nameView.text = roomName
holder.aliasView.setTextOrHide(roomAlias) holder.aliasView.setTextOrHide(roomAlias)
holder.topicView.setTextOrHide(roomTopic) holder.topicView.setTextOrHide(roomTopic)

View File

@ -28,9 +28,11 @@ import im.vector.riotredesign.core.epoxy.loadingItem
import im.vector.riotredesign.core.epoxy.noResultItem import im.vector.riotredesign.core.epoxy.noResultItem
import im.vector.riotredesign.core.error.ErrorFormatter import im.vector.riotredesign.core.error.ErrorFormatter
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.AvatarRenderer
import javax.inject.Inject import javax.inject.Inject


class PublicRoomsController @Inject constructor(private val stringProvider: StringProvider, class PublicRoomsController @Inject constructor(private val stringProvider: StringProvider,
private val avatarRenderer: AvatarRenderer,
private val errorFormatter: ErrorFormatter) : TypedEpoxyController<PublicRoomsViewState>() { private val errorFormatter: ErrorFormatter) : TypedEpoxyController<PublicRoomsViewState>() {


var callback: Callback? = null var callback: Callback? = null
@ -79,6 +81,7 @@ class PublicRoomsController @Inject constructor(private val stringProvider: Stri


private fun buildPublicRoom(publicRoom: PublicRoom, viewState: PublicRoomsViewState) { private fun buildPublicRoom(publicRoom: PublicRoom, viewState: PublicRoomsViewState) {
publicRoomItem { publicRoomItem {
avatarRenderer(avatarRenderer)
id(publicRoom.roomId) id(publicRoom.roomId)
roomId(publicRoom.roomId) roomId(publicRoom.roomId)
avatarUrl(publicRoom.avatarUrl) avatarUrl(publicRoom.avatarUrl)

View File

@ -47,6 +47,7 @@ class RoomPreviewNoPreviewFragment : VectorBaseFragment() {


@Inject lateinit var errorFormatter: ErrorFormatter @Inject lateinit var errorFormatter: ErrorFormatter
@Inject lateinit var roomPreviewViewModelFactory: RoomPreviewViewModel.Factory @Inject lateinit var roomPreviewViewModelFactory: RoomPreviewViewModel.Factory
@Inject lateinit var avatarRenderer: AvatarRenderer
private val roomPreviewViewModel: RoomPreviewViewModel by fragmentViewModel() private val roomPreviewViewModel: RoomPreviewViewModel by fragmentViewModel()
private val roomPreviewData: RoomPreviewData by args() private val roomPreviewData: RoomPreviewData by args()


@ -65,11 +66,11 @@ class RoomPreviewNoPreviewFragment : VectorBaseFragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)


// Toolbar // Toolbar
AvatarRenderer.render(roomPreviewData.avatarUrl, roomPreviewData.roomId, roomPreviewData.roomName, roomPreviewNoPreviewToolbarAvatar) avatarRenderer.render(roomPreviewData.avatarUrl, roomPreviewData.roomId, roomPreviewData.roomName, roomPreviewNoPreviewToolbarAvatar)
roomPreviewNoPreviewToolbarTitle.text = roomPreviewData.roomName roomPreviewNoPreviewToolbarTitle.text = roomPreviewData.roomName


// Screen // Screen
AvatarRenderer.render(roomPreviewData.avatarUrl, roomPreviewData.roomId, roomPreviewData.roomName, roomPreviewNoPreviewAvatar) avatarRenderer.render(roomPreviewData.avatarUrl, roomPreviewData.roomId, roomPreviewData.roomName, roomPreviewNoPreviewAvatar)
roomPreviewNoPreviewName.text = roomPreviewData.roomName roomPreviewNoPreviewName.text = roomPreviewData.roomName
roomPreviewNoPreviewTopic.setTextOrHide(roomPreviewData.topic) roomPreviewNoPreviewTopic.setTextOrHide(roomPreviewData.topic)



View File

@ -55,6 +55,7 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() {
private var testManager: NotificationTroubleshootTestManager? = null private var testManager: NotificationTroubleshootTestManager? = null
// members // members
@Inject lateinit var session: Session @Inject lateinit var session: Session
@Inject lateinit var bugReporter: BugReporter


override fun getLayoutResId() = R.layout.fragment_settings_notifications_troubleshoot override fun getLayoutResId() = R.layout.fragment_settings_notifications_troubleshoot


@ -78,7 +79,7 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() {




mSummaryButton.setOnClickListener { mSummaryButton.setOnClickListener {
BugReporter.openBugReportScreen(activity!!) bugReporter.openBugReportScreen(activity!!)
} }


mRunButton.setOnClickListener { mRunButton.setOnClickListener {

View File

@ -41,8 +41,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.HasInjector
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.utils.toast import im.vector.riotredesign.core.utils.toast
import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupActivity import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupActivity