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 olmManager: OlmManager
@Inject internal lateinit var sessionManager: SessionManager
var currentSession: Session? = null
init {
Monarchy.init(context)
@ -63,12 +62,6 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
}
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
userAgentHolder.setApplicationFlavor(matrixConfiguration.applicationFlavor)
authenticator.getLastActiveSession()?.also {
currentSession = it
it.open()
it.setFilter(FilterService.FilterPreset.RiotFilter)
it.startSync()
}
}
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.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.util.Cancelable
@ -40,14 +41,24 @@ interface Authenticator {
* Check if there is an 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.
/**
* Get the last active [Session], if there is an active session.
* @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.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.MatrixScope
import im.vector.matrix.android.internal.session.DaggerSessionComponent
@ -27,27 +28,18 @@ import im.vector.matrix.android.internal.session.SessionComponent
import javax.inject.Inject
@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>()
fun getSessionComponent(userId: String): SessionComponent? {
return sessionComponents[userId]
val sessionParams = sessionParamsStore.get(userId) ?: return null
return getOrCreateSessionComponent(sessionParams)
}
fun createSession(sessionParams: SessionParams): Session {
val userId = sessionParams.credentials.userId
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 getOrCreateSession(sessionParams: SessionParams): Session {
return getOrCreateSessionComponent(sessionParams).session()
}
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
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.ThreePidMedium
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.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import retrofit2.Retrofit
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 sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager
) : Authenticator {
override fun hasActiveSessions(): Boolean {
return sessionParamsStore.get() != null
override fun hasAuthenticatedSessions(): Boolean {
return sessionParamsStore.getLast() != null
}
override fun getLastActiveSession(): Session? {
val sessionParams = sessionParamsStore.get()
override fun getLastAuthenticatedSession(): Session? {
val sessionParams = sessionParamsStore.getLast()
return sessionParams?.let {
sessionManager.createSession(it)
sessionManager.getOrCreateSession(it)
}
}
override fun getSession(sessionParams: SessionParams): Session? {
return sessionManager.getOrCreateSession(sessionParams)
}
override fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig,
login: String,
password: String,
@ -84,13 +93,13 @@ internal class DefaultAuthenticator @Inject constructor(private val retrofitBuil
sessionParamsStore.save(sessionParams)
sessionParams
}.map {
sessionManager.createSession(it)
sessionManager.getOrCreateSession(it)
}
}
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)
}

View File

@ -21,9 +21,15 @@ import im.vector.matrix.android.api.auth.data.SessionParams
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 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.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.di.AuthDatabase
import im.vector.matrix.android.internal.di.MatrixScope
import io.realm.Realm
import io.realm.RealmConfiguration
import javax.inject.Inject
internal class RealmSessionParamsStore @Inject constructor(private val mapper: SessionParamsMapper,
@AuthDatabase private val realmConfiguration: RealmConfiguration
@AuthDatabase
private val realmConfiguration: RealmConfiguration
) : 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> {
return Try {
val entity = mapper.map(sessionParams)
@ -43,18 +76,20 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
}
}
override fun get(): 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 delete(userId: String): Try<Unit> {
return Try {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
it.where(SessionParamsEntity::class.java)
.equalTo(SessionParamsEntityFields.USER_ID, userId)
.findAll()
.deleteAllFromRealm()
}
realm.close()
}
}
override fun delete(): Try<Unit> {
override fun deleteAll(): Try<Unit> {
return Try {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {

View File

@ -17,8 +17,10 @@
package im.vector.matrix.android.internal.auth.db
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
internal open class SessionParamsEntity(
@PrimaryKey var userId: String = "",
var credentialsJson: String = "",
var homeServerConnectionConfigJson: String = ""
) : RealmObject()

View File

@ -49,7 +49,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
if (credentialsJson == null || homeServerConnectionConfigJson == 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 im.vector.matrix.android.api.Matrix
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.SessionParamsStore
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.util.BackgroundDetectionObserver
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
@ -42,8 +44,7 @@ internal interface MatrixComponent {
fun moshi(): Moshi
fun retrofitBuilder(): Retrofit.Builder
@Unauthenticated
fun okHttpClient(): OkHttpClient
fun authenticator(): Authenticator
@ -62,6 +63,8 @@ internal interface MatrixComponent {
fun backgroundDetectionObserver(): BackgroundDetectionObserver
fun sessionManager(): SessionManager
fun inject(matrix: Matrix)
@Component.Factory

View File

@ -66,9 +66,9 @@ internal object NetworkModule {
@MatrixScope
@Provides
@JvmStatic
@Unauthenticated
fun providesOkHttpClient(stethoInterceptor: StethoInterceptor,
userAgentInterceptor: UserAgentInterceptor,
accessTokenInterceptor: AccessTokenInterceptor,
httpLoggingInterceptor: HttpLoggingInterceptor,
curlLoggingInterceptor: CurlLoggingInterceptor,
okReplayInterceptor: OkReplayInterceptor): OkHttpClient {
@ -78,7 +78,6 @@ internal object NetworkModule {
.writeTimeout(30, TimeUnit.SECONDS)
.addNetworkInterceptor(stethoInterceptor)
.addInterceptor(userAgentInterceptor)
.addInterceptor(accessTokenInterceptor)
.addInterceptor(httpLoggingInterceptor)
.apply {
if (BuildConfig.LOG_PRIVATE_DATA) {
@ -94,15 +93,4 @@ internal object NetworkModule {
fun providesMoshi(): Moshi {
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
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.di.MatrixScope
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.session.SessionScope
import okhttp3.Interceptor
import okhttp3.Response
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 {
var request = chain.request()
val newRequestBuilder = request.newBuilder()
// Add the access token to all requests if it is set
val sessionParams = sessionParamsStore.get()
sessionParams?.let {
newRequestBuilder.addHeader(HttpHeaders.Authorization, "Bearer " + it.credentials.accessToken)
}
newRequestBuilder.addHeader(HttpHeaders.Authorization, "Bearer " + credentials.accessToken)
request = newRequestBuilder.build()
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.internal.database.LiveEntityObserver
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.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.room.EventRelationsAggregationUpdater
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.util.md5
import io.realm.RealmConfiguration
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import java.io.File
@ -86,13 +91,26 @@ internal abstract class SessionModule {
@JvmStatic
@Provides
@SessionScope
fun providesRetrofit(sessionParams: SessionParams, retrofitBuilder: Retrofit.Builder): Retrofit {
return retrofitBuilder
.baseUrl(sessionParams.homeServerConnectionConfig.homeServerUri.toString())
@Authenticated
fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient,
accessTokenInterceptor: AccessTokenInterceptor): OkHttpClient {
return okHttpClient.newBuilder()
.addInterceptor(accessTokenInterceptor)
.build()
}
@JvmStatic
@Provides
@SessionScope
fun providesRetrofit(@Authenticated okHttpClient: OkHttpClient,
sessionParams: SessionParams,
retrofitFactory: RetrofitFactory): Retrofit {
return retrofitFactory
.create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString())
}
}
@Binds
abstract fun bindSession(session: DefaultSession): Session

View File

@ -20,7 +20,9 @@ import arrow.core.Try
import arrow.core.Try.Companion.raise
import com.squareup.moshi.Moshi
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.Unauthenticated
import im.vector.matrix.android.internal.network.ProgressRequestBody
import im.vector.matrix.android.internal.session.SessionScope
import okhttp3.*
@ -29,7 +31,8 @@ import java.io.IOException
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 moshi: Moshi) {

View File

@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.database.query.where
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmQuery
import timber.log.Timber
import javax.inject.Inject
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
else -> baseQuery(chunkEntity.events, sender, unlinked).prev(since = event.stateIndex)?.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()
}

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

View File

@ -17,6 +17,9 @@
package im.vector.matrix.android.internal.session.signout
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.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
@ -25,14 +28,20 @@ import javax.inject.Inject
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 {
override suspend fun execute(params: Unit): Try<Unit> {
return executeRequest<Unit> {
apiCall = signOutAPI.signOut()
}.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.failure
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.MatrixError
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,
private val credentials: Credentials,
private val filterRepository: FilterRepository,
private val syncResponseHandler: SyncResponseHandler,
private val sessionParamsStore: SessionParamsStore
@ -58,7 +60,7 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI,
// Intercept 401
if (throwable is Failure.ServerError
&& throwable.error.code == MatrixError.UNKNOWN_TOKEN) {
sessionParamsStore.delete()
sessionParamsStore.delete(credentials.userId)
}
// Transmit the throwable

View File

@ -32,51 +32,55 @@ import com.github.piasy.biv.loader.glide.GlideImageLoader
import com.jakewharton.threetenabp.AndroidThreeTen
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixConfiguration
import im.vector.riotredesign.core.di.DaggerVectorComponent
import im.vector.riotredesign.core.di.HasInjector
import im.vector.riotredesign.core.di.VectorComponent
import im.vector.riotredesign.core.di.*
import im.vector.riotredesign.features.configuration.VectorConfiguration
import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks
import im.vector.riotredesign.features.rageshake.VectorFileLogger
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
import timber.log.Timber
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
//font thread handler
@Inject lateinit var vectorConfiguration: VectorConfiguration
@Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
lateinit var vectorComponent: VectorComponent
private var fontThreadHandler: Handler? = null
override fun onCreate() {
super.onCreate()
appContext = this
vectorComponent = DaggerVectorComponent.factory().create(this)
vectorComponent.inject(this)
VectorUncaughtExceptionHandler.activate(this)
// Log
VectorFileLogger.init(this)
Timber.plant(Timber.DebugTree(), VectorFileLogger)
if (BuildConfig.DEBUG) {
Stetho.initializeWithDefaults(this)
val time = measureTimeMillis {
super.onCreate()
appContext = this
vectorComponent = DaggerVectorComponent.factory().create(this)
vectorComponent.inject(this)
vectorUncaughtExceptionHandler.activate(this)
// Log
VectorFileLogger.init(this)
Timber.plant(Timber.DebugTree(), VectorFileLogger)
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)
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()
Timber.v("On create took $time ms")
}
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
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.KeysBackupSetupStep3Fragment
import im.vector.riotredesign.features.crypto.verification.SASVerificationIncomingFragment
import im.vector.riotredesign.features.home.HomeActivity
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.*
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.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.ViewReactionBottomSheet
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.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.roomdirectory.PublicRoomsFragment
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryActivity
@ -60,6 +63,10 @@ interface ScreenComponent {
fun viewModelFactory(): ViewModelProvider.Factory
fun bugReporter(): BugReporter
fun rageShake(): RageShake
fun inject(activity: HomeActivity)
fun inject(roomDetailFragment: RoomDetailFragment)
@ -118,6 +125,15 @@ interface ScreenComponent {
fun inject(roomDirectoryActivity: RoomDirectoryActivity)
fun inject(bugReportActivity: BugReportActivity)
fun inject(imageMediaViewerActivity: ImageMediaViewerActivity)
fun inject(vectorInviteView: VectorInviteView)
fun inject(videoMediaViewerActivity: VideoMediaViewerActivity)
@Component.Factory
interface Factory {
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.crypto.keysrequest.KeyRequestHandler
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.HomeRoomListObservableStore
import im.vector.riotredesign.features.home.group.SelectedGroupStore
import im.vector.riotredesign.features.navigation.Navigator
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
@Component(modules = [VectorModule::class])
@Singleton
interface VectorComponent {
fun inject(vectorApplication: VectorApplication)
fun matrix(): Matrix
fun currentSession(): Session
@ -51,6 +57,8 @@ interface VectorComponent {
fun vectorConfiguration(): VectorConfiguration
fun activeSessionHolder(): ActiveSessionHolder
fun emojiCompatFontProvider(): EmojiCompatFontProvider
fun navigator(): Navigator
@ -65,10 +73,12 @@ interface VectorComponent {
fun incomingKeyRequestHandler(): KeyRequestHandler
fun inject(vectorApplication: VectorApplication)
fun authenticator(): Authenticator
fun bugReporter(): BugReporter
fun vectorUncaughtExceptionHandler(): VectorUncaughtExceptionHandler
@Component.Factory
interface Factory {
fun create(@BindsInstance context: Context): VectorComponent

View File

@ -55,9 +55,9 @@ abstract class VectorModule {
@Provides
@JvmStatic
fun providesCurrentSession(matrix: Matrix): Session {
fun providesCurrentSession(activeSessionHolder: ActiveSessionHolder): Session {
//TODO: handle session injection better
return matrix.currentSession!!
return activeSessionHolder.getActiveSession()
}
@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.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.Unbinder
import com.airbnb.mvrx.BaseMvRxActivity
import com.airbnb.mvrx.MvRxState
import com.bumptech.glide.util.Util
import com.google.android.material.snackbar.Snackbar
import im.vector.riotredesign.BuildConfig
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.DaggerScreenComponent
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.di.*
import im.vector.riotredesign.core.utils.toast
import im.vector.riotredesign.features.configuration.VectorConfiguration
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.roomdirectory.PublicRoomsViewState
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryViewModel
import im.vector.riotredesign.features.themes.ActivityOtherThemes
import im.vector.riotredesign.features.themes.ThemeUtils
import im.vector.riotredesign.receivers.DebugReceiver
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import timber.log.Timber
import javax.inject.Provider
import kotlin.reflect.KClass
import kotlin.system.measureTimeMillis
abstract class VectorBaseActivity : BaseMvRxActivity(), HasInjector<ScreenComponent> {
abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
/* ==========================================================================================
* UI
* ========================================================================================== */
@ -81,6 +73,8 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasInjector<ScreenCompon
protected lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var configurationViewModel: ConfigurationViewModel
protected lateinit var bugReporter: BugReporter
private lateinit var rageShake: RageShake
private var unBinder: Unbinder? = null
@ -92,7 +86,6 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasInjector<ScreenCompon
private val uiDisposables = CompositeDisposable()
private val restorables = ArrayList<Restorable>()
private var rageShake: RageShake? = null
private lateinit var screenComponent: ScreenComponent
override fun attachBaseContext(base: Context) {
@ -125,9 +118,14 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasInjector<ScreenCompon
override fun onCreate(savedInstanceState: Bundle?) {
screenComponent = DaggerScreenComponent.factory().create(getVectorComponent(), this)
super.onCreate(savedInstanceState)
injectWith(screenComponent)
val timeForInjection = measureTimeMillis {
injectWith(screenComponent)
}
Timber.v("Injecting dependencies into ${javaClass.simpleName} took $timeForInjection ms")
viewModelFactory = screenComponent.viewModelFactory()
configurationViewModel = ViewModelProviders.of(this, viewModelFactory).get(ConfigurationViewModel::class.java)
bugReporter = screenComponent.bugReporter()
rageShake = screenComponent.rageShake()
configurationViewModel.activityRestarter.observe(this, Observer {
if (!it.hasBeenHandled) {
// Recreate the Activity because configuration has changed
@ -137,7 +135,6 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasInjector<ScreenCompon
})
// Shake detector
rageShake = RageShake(this)
ThemeUtils.setActivityTheme(this, getOtherThemes())
@ -215,7 +212,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasInjector<ScreenCompon
super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig)
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 {
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.bumptech.glide.util.Util.assertMainThread
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.features.navigation.Navigator
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import timber.log.Timber
abstract class VectorBaseFragment : BaseMvRxFragment(), OnBackPressed, HasInjector<ScreenComponent> {
abstract class VectorBaseFragment : BaseMvRxFragment(), OnBackPressed, HasScreenInjector {
// Butterknife unbinder
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.MatrixCallback
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.extensions.openAndStartSync
import im.vector.riotredesign.core.platform.VectorBaseActivity
import im.vector.riotredesign.features.home.HomeActivity
import im.vector.riotredesign.features.login.LoginActivity
@ -49,6 +51,7 @@ class MainActivity : VectorBaseActivity() {
@Inject lateinit var matrix: Matrix
@Inject lateinit var authenticator: Authenticator
@Inject lateinit var sessionHolder: ActiveSessionHolder
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
@ -58,30 +61,31 @@ class MainActivity : VectorBaseActivity() {
super.onCreate(savedInstanceState)
val clearCache = intent.getBooleanExtra(EXTRA_CLEAR_CACHE, false)
val clearCredentials = intent.getBooleanExtra(EXTRA_CLEAR_CREDENTIALS, false)
val session = matrix.currentSession
if (session == null) {
start()
} else {
// Handle some wanted cleanup
when {
clearCredentials -> session.signOut(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.w("SIGN_OUT: success, start app")
start()
}
})
clearCache -> session.clearCache(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
start()
}
})
else -> start()
}
// Handle some wanted cleanup
when {
clearCredentials -> sessionHolder.getActiveSession().signOut(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.w("SIGN_OUT: success, start app")
sessionHolder.clearActiveSession()
start()
}
})
clearCache -> sessionHolder.getActiveSession().clearCache(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
start()
}
})
else -> 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)
} else {
LoginActivity.newIntent(this)

View File

@ -29,6 +29,7 @@ import im.vector.riotredesign.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_autocomplete_user)
abstract class AutocompleteUserItem : VectorEpoxyModel<AutocompleteUserItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute var name: String? = null
@EpoxyAttribute var userId: String = ""
@EpoxyAttribute var avatarUrl: String? = null
@ -37,7 +38,7 @@ abstract class AutocompleteUserItem : VectorEpoxyModel<AutocompleteUserItem.Hold
override fun bind(holder: Holder) {
holder.view.setOnClickListener(clickListener)
holder.nameView.text = name
AvatarRenderer.render(avatarUrl, userId, name, holder.avatarImageView)
avatarRenderer.render(avatarUrl, userId, name, holder.avatarImageView)
}
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.SasVerificationTxState
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.features.popup.PopupAlertManager
import javax.inject.Inject
import javax.inject.Singleton
@ -31,11 +32,11 @@ import javax.inject.Singleton
*/
@Singleton
class IncomingVerificationRequestHandler @Inject constructor(val context: Context,
private val session: Session
private val activeSessionHolder: ActiveSessionHolder
) : SasVerificationService.SasVerificationListener {
init {
session.getSasVerificationService().addListener(this)
activeSessionHolder.getActiveSession().getSasVerificationService().addListener(this)
}
override fun transactionCreated(tx: SasVerificationTransaction) {}
@ -44,7 +45,7 @@ class IncomingVerificationRequestHandler @Inject constructor(val context: Contex
when (tx.state) {
SasVerificationTxState.OnStarted -> {
//Add a notification for every incoming request
val session = Matrix.getInstance(context).currentSession!!
val session = activeSessionHolder.getActiveSession()
val name = session.getUser(tx.otherUserId)?.displayName
?: 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.platform.VectorBaseFragment
import im.vector.riotredesign.features.home.AvatarRenderer
import javax.inject.Inject
class SASVerificationIncomingFragment : VectorBaseFragment() {
@ -48,6 +49,7 @@ class SASVerificationIncomingFragment : VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_sas_verification_incoming_request
@Inject lateinit var avatarRenderer: AvatarRenderer
private lateinit var viewModel: SasVerificationViewModel
override fun injectWith(injector: ScreenComponent) {
@ -66,10 +68,10 @@ class SASVerificationIncomingFragment : VectorBaseFragment() {
otherDeviceTextView.text = viewModel.otherDeviceId
viewModel.otherUser?.let {
AvatarRenderer.render(it, avatarImageView)
avatarRenderer.render(it, avatarImageView)
} ?: run {
// 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 {

View File

@ -26,29 +26,32 @@ import com.amulyakhare.textdrawable.TextDrawable
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.DrawableImageViewTarget
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.session.content.ContentUrlResolver
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.user.model.User
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.GlideRequest
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>
*/
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(
R.color.riotx_avatar_fill_1,
R.color.riotx_avatar_fill_2,
R.color.riotx_avatar_fill_3
)
private val AVATAR_COLOR_LIST = listOf(
R.color.riotx_avatar_fill_1,
R.color.riotx_avatar_fill_2,
R.color.riotx_avatar_fill_3
)
}
@UiThread
fun render(roomSummary: RoomSummary, imageView: ImageView) {
@ -78,7 +81,7 @@ object AvatarRenderer {
name
}
val placeholder = getPlaceholderDrawable(context, identifier, displayName)
buildGlideRequest(context, glideRequest, avatarUrl)
buildGlideRequest(glideRequest, avatarUrl)
.placeholder(placeholder)
.into(target)
}
@ -115,8 +118,8 @@ object AvatarRenderer {
// return AVATAR_COLOR_LIST[colorIndex.toInt()]
// }
private fun buildGlideRequest(context: Context, glideRequest: GlideRequests, avatarUrl: String?): GlideRequest<Drawable> {
val resolvedUrl = Matrix.getInstance(context).currentSession!!.contentUrlResolver()
private fun buildGlideRequest(glideRequest: GlideRequests, avatarUrl: String?): GlideRequest<Drawable> {
val resolvedUrl = activeSessionHolder.getActiveSession().contentUrlResolver()
.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)
return glideRequest

View File

@ -31,6 +31,7 @@ import androidx.lifecycle.ViewModelProviders
import com.airbnb.mvrx.viewModel
import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.extensions.hideKeyboard
import im.vector.riotredesign.core.extensions.observeEvent
@ -57,8 +58,10 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
private val homeActivityViewModel: HomeActivityViewModel by viewModel()
private lateinit var navigationViewModel: HomeNavigationViewModel
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var homeActivityViewModelFactory: HomeActivityViewModel.Factory
@Inject lateinit var homeNavigator: HomeNavigator
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
// TODO Move this elsewhere
@Inject lateinit var incomingVerificationRequestHandler: IncomingVerificationRequestHandler
// TODO Move this elsewhere
@ -119,14 +122,14 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
override fun onResume() {
super.onResume()
if (VectorUncaughtExceptionHandler.didAppCrash(this)) {
VectorUncaughtExceptionHandler.clearAppCrashStatus(this)
if (vectorUncaughtExceptionHandler.didAppCrash(this)) {
vectorUncaughtExceptionHandler.clearAppCrashStatus(this)
AlertDialog.Builder(this)
.setMessage(R.string.send_bug_report_app_crashed)
.setCancelable(false)
.setPositiveButton(R.string.yes) { _, _ -> BugReporter.openBugReportScreen(this) }
.setNegativeButton(R.string.no) { _, _ -> BugReporter.deleteCrashFile(this) }
.setPositiveButton(R.string.yes) { _, _ -> bugReporter.openBugReportScreen(this) }
.setNegativeButton(R.string.no) { _, _ -> bugReporter.deleteCrashFile(this) }
.show()
}
}
@ -140,7 +143,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.sliding_menu_sign_out -> {
SignOutUiWorker(this).perform(Matrix.getInstance(this).currentSession!!)
SignOutUiWorker(this).perform(activeSessionHolder.getActiveSession())
return true
}
}

View File

@ -70,6 +70,7 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate {
@Inject lateinit var session: Session
@Inject lateinit var homeDetailViewModelFactory: HomeDetailViewModel.Factory
@Inject lateinit var avatarRenderer: AvatarRenderer
override fun getLayoutResId(): Int {
return R.layout.fragment_home_detail
@ -138,7 +139,7 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate {
parentActivity.configure(groupToolbar)
}
groupToolbar.title = ""
AvatarRenderer.render(
avatarRenderer.render(
params.groupAvatar,
params.groupId,
params.groupName,

View File

@ -37,6 +37,7 @@ class HomeDrawerFragment : VectorBaseFragment() {
}
@Inject lateinit var session: Session
@Inject lateinit var avatarRenderer: AvatarRenderer
override fun getLayoutResId() = R.layout.fragment_home_drawer
@ -53,7 +54,7 @@ class HomeDrawerFragment : VectorBaseFragment() {
session.observeUser(session.sessionParams.credentials.userId).observeK(this) { user ->
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
homeDrawerUserIdView.text = user.userId
}

View File

@ -18,9 +18,10 @@ package im.vector.riotredesign.features.home.group
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.riotredesign.features.home.AvatarRenderer
import javax.inject.Inject
class GroupSummaryController @Inject constructor(): TypedEpoxyController<GroupListViewState>() {
class GroupSummaryController @Inject constructor(private val avatarRenderer: AvatarRenderer): TypedEpoxyController<GroupListViewState>() {
var callback: Callback? = null
@ -35,6 +36,7 @@ class GroupSummaryController @Inject constructor(): TypedEpoxyController<GroupLi
summaries.forEach { groupSummary ->
val isSelected = groupSummary.groupId == selected?.groupId
groupSummaryItem {
avatarRenderer(avatarRenderer)
id(groupSummary.groupId)
groupId(groupSummary.groupId)
groupName(groupSummary.displayName)

View File

@ -29,6 +29,7 @@ import im.vector.riotredesign.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_group)
abstract class GroupSummaryItem : VectorEpoxyModel<GroupSummaryItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var groupName: CharSequence
@EpoxyAttribute lateinit var groupId: String
@EpoxyAttribute var avatarUrl: String? = null
@ -40,7 +41,7 @@ abstract class GroupSummaryItem : VectorEpoxyModel<GroupSummaryItem.Holder>() {
holder.rootView.setOnClickListener { listener?.invoke() }
holder.groupNameView.text = groupName
holder.rootView.isChecked = selected
AvatarRenderer.render(avatarUrl, groupId, groupName.toString(), holder.avatarImageView)
avatarRenderer.render(avatarUrl, groupId, groupName.toString(), holder.avatarImageView)
}
class Holder : VectorEpoxyHolder() {

View File

@ -174,6 +174,7 @@ class RoomDetailFragment :
private val textComposerViewModel: TextComposerViewModel by fragmentViewModel()
@Inject lateinit var session: Session
@Inject lateinit var avatarRenderer: AvatarRenderer
@Inject lateinit var timelineEventController: TimelineEventController
@Inject lateinit var commandAutocompletePolicy: CommandAutocompletePolicy
@Inject lateinit var autocompleteCommandPresenter: AutocompleteCommandPresenter
@ -183,6 +184,7 @@ class RoomDetailFragment :
@Inject lateinit var textComposerViewModelFactory: TextComposerViewModel.Factory
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
override fun getLayoutResId() = R.layout.fragment_room_detail
private lateinit var actionViewModel: ActionsHandler
@ -223,7 +225,7 @@ class RoomDetailFragment :
commandAutocompletePolicy.enabled = true
val uid = session.sessionParams.credentials.userId
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()
}
SendMode.EDIT,
@ -270,7 +272,7 @@ class RoomDetailFragment :
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)
composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text.length)
@ -378,7 +380,7 @@ class RoomDetailFragment :
// Add the span
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)
editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
@ -487,7 +489,7 @@ class RoomDetailFragment :
val uid = session.sessionParams.credentials.userId
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) {
inviteView.visibility = View.VISIBLE
@ -500,7 +502,7 @@ class RoomDetailFragment :
private fun renderRoomSummary(state: RoomDetailViewState) {
state.asyncRoomSummary()?.let {
roomToolbarTitleView.text = it.displayName
AvatarRenderer.render(it, roomToolbarAvatarImageView)
avatarRenderer.render(it, roomToolbarAvatarImageView)
if (it.topic.isNotEmpty()) {
roomToolbarSubtitleView.visibility = View.VISIBLE
roomToolbarSubtitleView.text = it.topic

View File

@ -25,25 +25,14 @@ import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyController
import com.airbnb.epoxy.EpoxyModel
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.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.model.message.*
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.core.epoxy.LoadingItem_
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.helper.TimelineDateFormatter
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.helper.*
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
@ -56,7 +45,9 @@ import javax.inject.Inject
class TimelineEventController @Inject constructor(private val dateFormatter: TimelineDateFormatter,
private val timelineItemFactory: TimelineItemFactory,
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
@TimelineEventControllerHandler private val backgroundHandler: Handler
private val avatarRenderer: AvatarRenderer,
@TimelineEventControllerHandler
private val backgroundHandler: Handler
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener {
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
// We then are sure we always have items up to date.
if (modelCache[position] == null
|| modelCache[position]?.mergedHeaderModel != null
|| modelCache[position]?.formattedDayModel != null) {
|| modelCache[position]?.mergedHeaderModel != null
|| modelCache[position]?.formattedDayModel != null) {
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
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey)
?: true
?: true
val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState }
if (isCollapsed) {
collapsedEventIds.addAll(mergedEventIds)
@ -269,7 +260,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
collapsedEventIds.removeAll(mergedEventIds)
}
val mergeId = mergedEventIds.joinToString(separator = "_") { it }
MergedHeaderItem(isCollapsed, mergeId, mergedData) {
MergedHeaderItem(isCollapsed, mergeId, mergedData, avatarRenderer) {
mergeItemCollapseStates[event.localId] = it
requestModelBuild()
}

View File

@ -46,6 +46,7 @@ import javax.inject.Inject
class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
@Inject lateinit var messageActionViewModelFactory: MessageActionsViewModel.Factory
@Inject lateinit var avatarRenderer: AvatarRenderer
private val viewModel: MessageActionsViewModel by fragmentViewModel(MessageActionsViewModel::class)
private lateinit var actionHandlerModel: ActionsHandler
@ -127,7 +128,7 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
senderNameTextView.text = it.senderName
messageBodyTextView.text = it.messageBody
messageTimestampText.text = it.ts
AvatarRenderer.render(it.senderAvatarPath, it.userId, it.senderName, senderAvatarImageView)
avatarRenderer.render(it.senderAvatarPath, it.userId, it.senderName, senderAvatarImageView)
} else {
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.core.epoxy.VectorEpoxyModel
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.senderName
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
// 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<*>? {
return when {
@ -59,6 +61,7 @@ class EncryptedItemFactory @Inject constructor(private val stringProvider: Strin
showInformation = false
)
return NoticeItem_()
.avatarRenderer(avatarRenderer)
.noticeText(spannableStr)
.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.riotredesign.R
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.senderName
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 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? {
val text = buildNoticeText(event.root, event.senderName) ?: return null
@ -43,6 +45,7 @@ class EncryptionItemFactory @Inject constructor(private val stringProvider: Stri
showInformation = false
)
return NoticeItem_()
.avatarRenderer(avatarRenderer)
.noticeText(text)
.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.toModel
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.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.model.message.*
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
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.StringProvider
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.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.TimelineMediaSizeProvider
import im.vector.riotredesign.features.home.room.detail.timeline.item.BlankItem_
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.home.room.detail.timeline.item.*
import im.vector.riotredesign.features.html.EventHtmlRenderer
import im.vector.riotredesign.features.media.ImageContentRenderer
import im.vector.riotredesign.features.media.VideoContentRenderer
@ -71,12 +54,15 @@ import me.gujun.android.span.span
import javax.inject.Inject
class MessageItemFactory @Inject constructor(
private val avatarRenderer: AvatarRenderer,
private val colorProvider: ColorProvider,
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
private val timelineDateFormatter: TimelineDateFormatter,
private val htmlRenderer: EventHtmlRenderer,
private val stringProvider: StringProvider,
private val emojiCompatFontProvider: EmojiCompatFontProvider) {
private val emojiCompatFontProvider: EmojiCompatFontProvider,
private val imageContentRenderer: ImageContentRenderer,
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder) {
fun create(event: TimelineEvent,
nextEvent: TimelineEvent?,
@ -89,33 +75,33 @@ class MessageItemFactory @Inject constructor(
val nextDate = nextEvent?.root?.localDateTime()
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
?: false
?: false
val showInformation = addDaySeparator
|| event.senderAvatar != nextEvent?.senderAvatar
|| event.senderName != nextEvent?.senderName
|| nextEvent?.root?.getClearType() != EventType.MESSAGE
|| isNextMessageReceivedMoreThanOneHourAgo
|| event.senderAvatar != nextEvent?.senderAvatar
|| event.senderName != nextEvent?.senderName
|| nextEvent?.root?.getClearType() != EventType.MESSAGE
|| isNextMessageReceivedMoreThanOneHourAgo
val time = timelineDateFormatter.formatMessageHour(date)
val avatarUrl = event.senderAvatar
val memberName = event.senderName ?: event.root.sender ?: ""
val formattedMemberName = span(memberName) {
textColor = colorProvider.getColor(getColorFromUserId(event.root.sender
?: ""))
?: ""))
}
val hasBeenEdited = event.annotations?.editSummary != null
val informationData = MessageInformationData(eventId = eventId,
senderId = event.root.sender ?: "",
sendState = event.sendState,
time = time,
avatarUrl = avatarUrl,
memberName = formattedMemberName,
showInformation = showInformation,
orderedReactionList = event.annotations?.reactionsSummary?.map {
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
},
hasBeenEdited = hasBeenEdited
senderId = event.root.sender ?: "",
sendState = event.sendState,
time = time,
avatarUrl = avatarUrl,
memberName = formattedMemberName,
showInformation = showInformation,
orderedReactionList = event.annotations?.reactionsSummary?.map {
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
},
hasBeenEdited = hasBeenEdited
)
if (event.root.unsignedData?.redactedEvent != null) {
@ -125,9 +111,9 @@ class MessageItemFactory @Inject constructor(
val messageContent: MessageContent =
event.annotations?.editSummary?.aggregatedContent?.toModel()
?: event.root.getClearContent().toModel()
?: //Malformed content, we should echo something on screen
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))
?: event.root.getClearContent().toModel()
?: //Malformed content, we should echo something on screen
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))
if (messageContent.relatesTo?.type == RelationType.REPLACE) {
// ignore replace event, the targeted id is already edited
@ -137,16 +123,16 @@ class MessageItemFactory @Inject constructor(
// val ev = all.toModel<Event>()
return when (messageContent) {
is MessageEmoteContent -> buildEmoteMessageItem(messageContent,
informationData,
hasBeenEdited,
event.annotations?.editSummary,
callback)
informationData,
hasBeenEdited,
event.annotations?.editSummary,
callback)
is MessageTextContent -> buildTextMessageItem(event.sendState,
messageContent,
informationData,
hasBeenEdited,
event.annotations?.editSummary,
callback
messageContent,
informationData,
hasBeenEdited,
event.annotations?.editSummary,
callback
)
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback)
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback)
@ -161,6 +147,7 @@ class MessageItemFactory @Inject constructor(
informationData: MessageInformationData,
callback: TimelineEventController.Callback?): MessageFileItem? {
return MessageFileItem_()
.avatarRenderer(avatarRenderer)
.informationData(informationData)
.avatarCallback(callback)
.filename(messageContent.body)
@ -177,7 +164,7 @@ class MessageItemFactory @Inject constructor(
}))
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
}
@ -185,6 +172,7 @@ class MessageItemFactory @Inject constructor(
informationData: MessageInformationData,
callback: TimelineEventController.Callback?): MessageFileItem? {
return MessageFileItem_()
.avatarRenderer(avatarRenderer)
.informationData(informationData)
.avatarCallback(callback)
.filename(messageContent.body)
@ -197,7 +185,7 @@ class MessageItemFactory @Inject constructor(
}))
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
.clickListener(
DebouncedClickListener(View.OnClickListener { _ ->
@ -226,6 +214,9 @@ class MessageItemFactory @Inject constructor(
rotation = messageContent.info?.rotation
)
return MessageImageVideoItem_()
.avatarRenderer(avatarRenderer)
.imageContentRenderer(imageContentRenderer)
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
.playable(messageContent.info?.mimeType == "image/gif")
.informationData(informationData)
.avatarCallback(callback)
@ -242,7 +233,7 @@ class MessageItemFactory @Inject constructor(
}))
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
}
@ -267,6 +258,9 @@ class MessageItemFactory @Inject constructor(
)
return MessageImageVideoItem_()
.imageContentRenderer(imageContentRenderer)
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
.avatarRenderer(avatarRenderer)
.playable(true)
.informationData(informationData)
.avatarCallback(callback)
@ -280,7 +274,7 @@ class MessageItemFactory @Inject constructor(
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) }
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
}
@ -306,6 +300,7 @@ class MessageItemFactory @Inject constructor(
message(linkifiedBody)
}
}
.avatarRenderer(avatarRenderer)
.informationData(informationData)
.avatarCallback(callback)
.reactionPillCallback(callback)
@ -317,7 +312,7 @@ class MessageItemFactory @Inject constructor(
}))
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
}
@ -349,9 +344,9 @@ class MessageItemFactory @Inject constructor(
//nop
}
},
editStart,
editEnd,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
editStart,
editEnd,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
return spannable
}
@ -368,6 +363,7 @@ class MessageItemFactory @Inject constructor(
linkifyBody(formattedBody, callback)
}
return MessageTextItem_()
.avatarRenderer(avatarRenderer)
.message(message)
.informationData(informationData)
.avatarCallback(callback)
@ -383,7 +379,7 @@ class MessageItemFactory @Inject constructor(
}))
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
}
@ -406,6 +402,7 @@ class MessageItemFactory @Inject constructor(
message(message)
}
}
.avatarRenderer(avatarRenderer)
.informationData(informationData)
.avatarCallback(callback)
.reactionPillCallback(callback)
@ -416,13 +413,14 @@ class MessageItemFactory @Inject constructor(
}))
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
}
private fun buildRedactedItem(informationData: MessageInformationData,
callback: TimelineEventController.Callback?): RedactedMessageItem? {
return RedactedMessageItem_()
.avatarRenderer(avatarRenderer)
.informationData(informationData)
.avatarCallback(callback)
}

View File

@ -17,6 +17,7 @@
package im.vector.riotredesign.features.home.room.detail.timeline.factory
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.format.NoticeEventFormatter
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 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,
callback: TimelineEventController.Callback?): NoticeItem? {
@ -41,6 +43,7 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv
)
return NoticeItem_()
.avatarRenderer(avatarRenderer)
.noticeText(formattedText)
.informationData(informationData)
.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.session.content.ContentUploadStateTracker
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.features.media.ImageContentRenderer
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>()
@ -36,7 +38,7 @@ object ContentUploadStateTrackerBinder {
mediaData: ImageContentRenderer.Data,
progressLayout: ViewGroup) {
Matrix.getInstance(progressLayout.context).currentSession?.also { session ->
activeSessionHolder.getActiveSession().also { session ->
val uploadStateTracker = session.contentUploadProgressTracker()
val updateListener = ContentMediaProgressUpdater(progressLayout, mediaData)
updateListeners[eventId] = updateListener
@ -44,8 +46,8 @@ object ContentUploadStateTrackerBinder {
}
}
fun unbind(eventId: String, progressLayout: ViewGroup) {
Matrix.getInstance(progressLayout.context).currentSession?.also { session ->
fun unbind(eventId: String) {
activeSessionHolder.getActiveSession().also { session ->
val uploadStateTracker = session.contentUploadProgressTracker()
updateListeners[eventId]?.also {
uploadStateTracker.untrack(eventId, it)

View File

@ -40,6 +40,8 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
abstract val informationData: MessageInformationData
abstract val avatarRenderer: AvatarRenderer
@EpoxyAttribute
var longClickListener: View.OnLongClickListener? = null
@ -96,7 +98,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
holder.timeView.visibility = View.VISIBLE
holder.timeView.text = informationData.time
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.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,
private val mergeId: String,
private val mergeData: List<Data>,
private val avatarRenderer: AvatarRenderer,
private val onCollapsedStateChanged: (Boolean) -> Unit
) : BaseEventItem<MergedHeaderItem.Holder>() {
@ -63,7 +64,7 @@ data class MergedHeaderItem(private val isCollapsed: Boolean,
val data = distinctMergeData.getOrNull(index)
if (data != null && view is ImageView) {
view.visibility = View.VISIBLE
AvatarRenderer.render(data.avatarUrl, data.userId, data.memberName, view)
avatarRenderer.render(data.avatarUrl, data.userId, data.memberName, view)
} else {
view.visibility = View.GONE
}

View File

@ -25,6 +25,7 @@ import androidx.annotation.DrawableRes
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R
import im.vector.riotredesign.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
@ -37,6 +38,8 @@ abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
@EpoxyAttribute
override lateinit var informationData: MessageInformationData
@EpoxyAttribute
override lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
var clickListener: View.OnClickListener? = null
override fun bind(holder: Holder) {

View File

@ -22,6 +22,7 @@ import android.widget.ImageView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
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.media.ImageContentRenderer
@ -33,14 +34,20 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
@EpoxyAttribute
override lateinit var informationData: MessageInformationData
@EpoxyAttribute
override lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
var playable: Boolean = false
@EpoxyAttribute
var clickListener: View.OnClickListener? = null
@EpoxyAttribute
lateinit var imageContentRenderer: ImageContentRenderer
@EpoxyAttribute
lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder
override fun bind(holder: Holder) {
super.bind(holder)
ImageContentRenderer.render(mediaData, ImageContentRenderer.Mode.THUMBNAIL, holder.imageView)
ContentUploadStateTrackerBinder.bind(informationData.eventId, mediaData, holder.progressLayout)
imageContentRenderer.render(mediaData, ImageContentRenderer.Mode.THUMBNAIL, holder.imageView)
contentUploadStateTrackerBinder.bind(informationData.eventId, mediaData, holder.progressLayout)
holder.imageView.setOnClickListener(clickListener)
holder.imageView.setOnLongClickListener(longClickListener)
holder.mediaContentView.setOnClickListener(cellClickListener)
@ -50,7 +57,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
}
override fun unbind(holder: Holder) {
ContentUploadStateTrackerBinder.unbind(informationData.eventId, holder.progressLayout)
contentUploadStateTrackerBinder.unbind(informationData.eventId)
super.unbind(holder)
}

View File

@ -24,6 +24,7 @@ import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R
import im.vector.riotredesign.core.utils.containsOnlyEmojis
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.html.PillImageSpan
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@ -37,6 +38,8 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
@EpoxyAttribute
var message: CharSequence? = null
@EpoxyAttribute
override lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
override lateinit var informationData: MessageInformationData
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)
abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
@EpoxyAttribute
lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
var noticeText: CharSequence? = null
@ -46,7 +49,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
override fun bind(holder: Holder) {
super.bind(holder)
holder.noticeTextView.text = noticeText
AvatarRenderer.render(
avatarRenderer.render(
informationData.avatarUrl,
informationData.senderId,
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.EpoxyModelClass
import im.vector.riotredesign.R
import im.vector.riotredesign.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class RedactedMessageItem : AbsMessageItem<RedactedMessageItem.Holder>() {
@EpoxyAttribute
override lateinit var informationData: MessageInformationData
@EpoxyAttribute
override lateinit var avatarRenderer: AvatarRenderer
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.resources.DateProvider
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.helper.TimelineDateFormatter
import javax.inject.Inject
class RoomSummaryController @Inject constructor(private val stringProvider: StringProvider,
private val eventFormatter: NoticeEventFormatter,
private val timelineDateFormatter: TimelineDateFormatter
private val timelineDateFormatter: TimelineDateFormatter,
private val avatarRenderer: AvatarRenderer
) : TypedEpoxyController<RoomListViewState>() {
var callback: Callback? = null
@ -110,6 +112,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
}
roomSummaryItem {
avatarRenderer(avatarRenderer)
id(roomSummary.roomId)
roomId(roomSummary.roomId)
lastEventTime(lastMessageTime)

View File

@ -30,6 +30,7 @@ import im.vector.riotredesign.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_room)
abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var roomName: CharSequence
@EpoxyAttribute lateinit var roomId: String
@EpoxyAttribute lateinit var lastFormattedEvent: CharSequence
@ -47,7 +48,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
holder.lastEventTimeView.text = lastEventTime
holder.lastEventView.text = lastFormattedEvent
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() {

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.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.glide.GlideRequests
import im.vector.riotredesign.features.home.AvatarRenderer
import org.commonmark.node.BlockQuote
import org.commonmark.node.HtmlBlock
import org.commonmark.node.HtmlInline
import org.commonmark.node.Node
import ru.noties.markwon.AbstractMarkwonPlugin
import ru.noties.markwon.Markwon
import ru.noties.markwon.MarkwonConfiguration
import ru.noties.markwon.MarkwonVisitor
import ru.noties.markwon.SpannableBuilder
import ru.noties.markwon.*
import ru.noties.markwon.html.HtmlTag
import ru.noties.markwon.html.MarkwonHtmlParserImpl
import ru.noties.markwon.html.MarkwonHtmlRenderer
import ru.noties.markwon.html.TagHandler
import ru.noties.markwon.html.tag.BlockquoteHandler
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 ru.noties.markwon.html.tag.*
import java.util.Arrays.asList
import javax.inject.Inject
class EventHtmlRenderer @Inject constructor(context: AppCompatActivity,
val avatarRenderer: AvatarRenderer,
session: Session) {
private val markwon = Markwon.builder(context)
.usePlugin(MatrixPlugin.create(GlideApp.with(context), context, session))
.usePlugin(MatrixPlugin.create(GlideApp.with(context), context, avatarRenderer, session))
.build()
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 val context: Context,
private val avatarRenderer: AvatarRenderer,
private val session: Session) : AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
@ -80,7 +69,7 @@ private class MatrixPlugin private constructor(private val glideRequests: GlideR
ImageHandler.create())
.setHandler(
"a",
MxLinkHandler(glideRequests, context, session))
MxLinkHandler(glideRequests, context, avatarRenderer, session))
.setHandler(
"blockquote",
BlockquoteHandler())
@ -112,7 +101,7 @@ private class MatrixPlugin private constructor(private val glideRequests: GlideR
asList<String>("h1", "h2", "h3", "h4", "h5", "h6"),
HeadingHandler())
.setHandler("mx-reply",
MxReplyTagHandler())
MxReplyTagHandler())
}
@ -135,14 +124,15 @@ private class MatrixPlugin private constructor(private val glideRequests: GlideR
companion object {
fun create(glideRequests: GlideRequests, context: Context, session: Session): MatrixPlugin {
return MatrixPlugin(glideRequests, context, session)
fun create(glideRequests: GlideRequests, context: Context, avatarRenderer: AvatarRenderer, session: Session): MatrixPlugin {
return MatrixPlugin(glideRequests, context, avatarRenderer, session)
}
}
}
private class MxLinkHandler(private val glideRequests: GlideRequests,
private val context: Context,
private val avatarRenderer: AvatarRenderer,
private val session: Session) : TagHandler() {
private val linkHandler = LinkHandler()
@ -154,7 +144,7 @@ private class MxLinkHandler(private val glideRequests: GlideRequests,
when (permalinkData) {
is PermalinkData.UserLink -> {
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(
visitor.builder(),
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.
*/
class PillImageSpan(private val glideRequests: GlideRequests,
private val avatarRenderer: AvatarRenderer,
private val context: Context,
private val userId: String,
private val user: User?) : ReplacementSpan() {
@ -52,7 +53,7 @@ class PillImageSpan(private val glideRequests: GlideRequests,
@UiThread
fun bind(textView: TextView) {
tv = WeakReference(textView)
AvatarRenderer.render(context, glideRequests, user?.avatarUrl, userId, displayName, target)
avatarRenderer.render(context, glideRequests, user?.avatarUrl, userId, displayName, target)
}
// ReplacementSpan *****************************************************************************
@ -105,7 +106,7 @@ class PillImageSpan(private val glideRequests: GlideRequests,
textStartPadding = textPadding
setChipMinHeightResource(R.dimen.pill_min_height)
setChipIconSizeResource(R.dimen.pill_avatar_size)
chipIcon = AvatarRenderer.getPlaceholderDrawable(context, userId, displayName)
chipIcon = avatarRenderer.getPlaceholderDrawable(context, userId, displayName)
setBounds(0, 0, intrinsicWidth, intrinsicHeight)
}
}

View File

@ -24,8 +24,10 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.updateLayoutParams
import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.HasScreenInjector
import im.vector.riotredesign.features.home.AvatarRenderer
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)
: ConstraintLayout(context, attrs, defStyle) {
@ -40,9 +42,13 @@ class VectorInviteView @JvmOverloads constructor(context: Context, attrs: Attrib
SMALL
}
@Inject lateinit var avatarRenderer: AvatarRenderer
var callback: Callback? = null
init {
if(context is HasScreenInjector){
context.injector().inject(this)
}
View.inflate(context, R.layout.vector_invite_view, this)
setBackgroundColor(Color.WHITE)
inviteRejectView.setOnClickListener { callback?.onRejectInvite() }
@ -52,7 +58,7 @@ class VectorInviteView @JvmOverloads constructor(context: Context, attrs: Attrib
fun render(sender: User, mode: Mode = Mode.LARGE) {
if (mode == Mode.LARGE) {
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
inviteNameView.text = sender.displayName
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.sync.FilterService
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
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.platform.VectorBaseActivity
import im.vector.riotredesign.features.home.HomeActivity
@ -47,6 +49,7 @@ private const val DEFAULT_ANTIVIRUS_SERVER_URI = "https://matrix.org"
class LoginActivity : VectorBaseActivity() {
@Inject lateinit var authenticator: Authenticator
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
private var passwordShown = false
@ -78,11 +81,8 @@ class LoginActivity : VectorBaseActivity() {
progressBar.visibility = View.VISIBLE
authenticator.authenticate(homeServerConnectionConfig, login, password, object : MatrixCallback<Session> {
override fun onSuccess(data: Session) {
Matrix.getInstance(this@LoginActivity).currentSession = data
data.open()
data.setFilter(FilterService.FilterPreset.RiotFilter)
data.startSync()
activeSessionHolder.setActiveSession(data)
data.openAndStartSync()
goToHome()
}

View File

@ -24,12 +24,14 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.github.piasy.biv.view.BigImageView
import im.vector.matrix.android.api.Matrix
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.utils.DimensionUtils.dpToPx
import kotlinx.android.parcel.Parcelize
import java.io.File
import javax.inject.Inject
object ImageContentRenderer {
class ImageContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder) {
@Parcelize
data class Data(
@ -57,26 +59,26 @@ object ImageContentRenderer {
val (width, height) = processSize(data, mode)
imageView.layoutParams.height = height
imageView.layoutParams.width = width
val contentUrlResolver = Matrix.getInstance(imageView.context).currentSession!!.contentUrlResolver()
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
val resolvedUrl = when (mode) {
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
}
//Fallback to base url
?: data.url
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
}
//Fallback to base url
?: data.url
GlideApp
.with(imageView)
.load(resolvedUrl)
.dontAnimate()
.transform(RoundedCorners(dpToPx(8,imageView.context)))
.transform(RoundedCorners(dpToPx(8, imageView.context)))
.thumbnail(0.3f)
.into(imageView)
}
fun render(data: Data, imageView: BigImageView) {
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 thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
imageView.showImage(

View File

@ -24,12 +24,20 @@ import android.os.Bundle
import androidx.appcompat.widget.Toolbar
import com.github.piasy.biv.indicator.progresspie.ProgressPieIndicator
import com.github.piasy.biv.view.GlideImageViewFactory
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.platform.VectorBaseActivity
import kotlinx.android.synthetic.main.activity_image_media_viewer.*
import javax.inject.Inject
class ImageMediaViewerActivity : VectorBaseActivity() {
@Inject lateinit var imageContentRenderer: ImageContentRenderer
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(im.vector.riotredesign.R.layout.activity_image_media_viewer)
@ -40,7 +48,7 @@ class ImageMediaViewerActivity : VectorBaseActivity() {
configureToolbar(imageMediaViewerToolbar, mediaData)
imageMediaViewerImageView.setImageViewFactory(GlideImageViewFactory())
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.VideoView
import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.core.di.ActiveSessionHolder
import kotlinx.android.parcel.Parcelize
import javax.inject.Inject
object VideoContentRenderer {
class VideoContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder){
@Parcelize
data class Data(
@ -32,7 +34,7 @@ object VideoContentRenderer {
) : Parcelable
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)
videoView.setVideoPath(resolvedUrl)
videoView.start()

View File

@ -20,12 +20,21 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.widget.Toolbar
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.platform.VectorBaseActivity
import im.vector.riotredesign.features.home.AvatarRenderer
import kotlinx.android.synthetic.main.activity_video_media_viewer.*
import javax.inject.Inject
class VideoMediaViewerActivity : VectorBaseActivity() {
@Inject lateinit var videoContentRenderer: VideoContentRenderer
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(im.vector.riotredesign.R.layout.activity_video_media_viewer)
@ -34,7 +43,7 @@ class VideoMediaViewerActivity : VectorBaseActivity() {
finish()
} else {
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.OnTextChanged
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.platform.VectorBaseActivity
import kotlinx.android.synthetic.main.activity_bug_report.*
import timber.log.Timber
import javax.inject.Inject
/**
* Form to send a bug report
@ -66,13 +68,17 @@ class BugReportActivity : VectorBaseActivity() {
@BindView(R.id.bug_report_mask_view)
lateinit var mMaskView: View
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
override fun getLayoutRes() = R.layout.activity_bug_report
override fun initUiAndData() {
configureToolbar(bugReportToolbar)
if (BugReporter.screenshot != null) {
mScreenShotPreview.setImageBitmap(BugReporter.screenshot)
if (bugReporter.screenshot != null) {
mScreenShotPreview.setImageBitmap(bugReporter.screenshot)
} else {
mScreenShotPreview.isVisible = false
mIncludeScreenShotButton.isChecked = false
@ -120,7 +126,7 @@ class BugReportActivity : VectorBaseActivity() {
mProgressBar.isVisible = true
mProgressBar.progress = 0
BugReporter.sendBugReport(this,
bugReporter.sendBugReport(this,
mIncludeLogsButton.isChecked,
mIncludeCrashLogsButton.isChecked,
mIncludeScreenShotButton.isChecked,
@ -190,12 +196,12 @@ class BugReportActivity : VectorBaseActivity() {
@OnCheckedChanged(R.id.bug_report_button_include_screenshot)
internal fun onSendScreenshotChanged() {
mScreenShotPreview.isVisible = mIncludeScreenShotButton.isChecked && BugReporter.screenshot != null
mScreenShotPreview.isVisible = mIncludeScreenShotButton.isChecked && bugReporter.screenshot != null
}
override fun onBackPressed() {
// Ensure there is no crash status remaining, which will be sent later on by mistake
BugReporter.deleteCrashFile(this)
bugReporter.deleteCrashFile(this)
super.onBackPressed()
}

View File

@ -30,6 +30,7 @@ import android.view.View
import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.BuildConfig
import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.ActiveSessionHolder
import im.vector.riotredesign.core.extensions.toOnOff
import im.vector.riotredesign.core.utils.getDeviceLocale
import im.vector.riotredesign.features.settings.VectorLocale
@ -42,19 +43,26 @@ import java.io.*
import java.net.HttpURLConnection
import java.util.*
import java.util.zip.GZIPOutputStream
import javax.inject.Inject
import javax.inject.Singleton
/**
* BugReporter creates and sends the bug reports.
*/
object BugReporter {
@Singleton
class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSessionHolder){
var inMultiWindowMode = false
// filenames
private const val LOG_CAT_ERROR_FILENAME = "logcatError.log"
private const val LOG_CAT_FILENAME = "logcat.log"
private const val LOG_CAT_SCREENSHOT_FILENAME = "screenshot.png"
private const val CRASH_FILENAME = "crash.log"
companion object {
// filenames
private const val LOG_CAT_ERROR_FILENAME = "logcatError.log"
private const val LOG_CAT_FILENAME = "logcat.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
private val mOkHttpClient = OkHttpClient()
@ -74,8 +82,6 @@ object BugReporter {
var screenshot: Bitmap? = null
private set
private const val BUFFER_SIZE = 1024 * 1024 * 50
private val LOGCAT_CMD_ERROR = arrayOf("logcat", ///< Run 'logcat' command
"-d", ///< Dump the log rather than continue outputting it
"-v", // formatting
@ -195,7 +201,7 @@ object BugReporter {
var matrixSdkVersion = "undefined"
var olmVersion = "undefined"
Matrix.getInstance(context).currentSession?.let { session ->
activeSessionHolder.getActiveSession().let { session ->
userId = session.sessionParams.credentials.userId
deviceId = session.sessionParams.credentials.deviceId ?: "undefined"
// TODO matrixSdkVersion = session.getVersion(true);

View File

@ -16,7 +16,6 @@
package im.vector.riotredesign.features.rageshake
import android.app.Activity
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorManager
@ -26,8 +25,10 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import com.squareup.seismic.ShakeDetector
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
@ -94,7 +95,7 @@ class RageShake(val activity: Activity) : ShakeDetector.Listener {
}
private fun openBugReportScreen() {
BugReporter.openBugReportScreen(activity)
bugReporter.openBugReportScreen(activity)
}
companion object {

View File

@ -16,7 +16,6 @@
package im.vector.riotredesign.features.rageshake
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import androidx.core.content.edit
@ -25,12 +24,16 @@ import im.vector.riotredesign.BuildConfig
import timber.log.Timber
import java.io.PrintWriter
import java.io.StringWriter
import javax.inject.Inject
import javax.inject.Singleton
@SuppressLint("StaticFieldLeak")
object VectorUncaughtExceptionHandler : Thread.UncaughtExceptionHandler {
@Singleton
class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter: BugReporter) : Thread.UncaughtExceptionHandler {
// 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 matrixSdkVersion: String = ""
@ -44,9 +47,7 @@ object VectorUncaughtExceptionHandler : Thread.UncaughtExceptionHandler {
*/
fun activate(context: Context) {
this.context = context
previousHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler(this)
}
@ -58,15 +59,10 @@ object VectorUncaughtExceptionHandler : Thread.UncaughtExceptionHandler {
* @return the exception description
*/
override fun uncaughtException(thread: Thread, throwable: Throwable) {
if (context == null) {
previousHandler?.uncaughtException(thread, throwable)
return
}
Timber.v("Uncaught exception: $throwable")
PreferenceManager.getDefaultSharedPreferences(context).edit {
putBoolean(PREFS_CRASH_KEY, true)
}
val b = StringBuilder()
val appName = "RiotX" // TODO Matrix.getApplicationName()
@ -114,7 +110,7 @@ object VectorUncaughtExceptionHandler : Thread.UncaughtExceptionHandler {
val bugDescription = b.toString()
BugReporter.saveCrashReport(context, bugDescription)
bugReporter.saveCrashReport(context, bugDescription)
// Show the classical system popup
previousHandler?.uncaughtException(thread, throwable)

View File

@ -31,6 +31,9 @@ import im.vector.riotredesign.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_public_room)
abstract class PublicRoomItem : VectorEpoxyModel<PublicRoomItem.Holder>() {
@EpoxyAttribute
lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
var avatarUrl: String? = null
@ -61,7 +64,7 @@ abstract class PublicRoomItem : VectorEpoxyModel<PublicRoomItem.Holder>() {
override fun bind(holder: Holder) {
holder.rootView.setOnClickListener { globalListener?.invoke() }
AvatarRenderer.render(avatarUrl, roomId!!, roomName, holder.avatarView)
avatarRenderer.render(avatarUrl, roomId!!, roomName, holder.avatarView)
holder.nameView.text = roomName
holder.aliasView.setTextOrHide(roomAlias)
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.error.ErrorFormatter
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.AvatarRenderer
import javax.inject.Inject
class PublicRoomsController @Inject constructor(private val stringProvider: StringProvider,
private val avatarRenderer: AvatarRenderer,
private val errorFormatter: ErrorFormatter) : TypedEpoxyController<PublicRoomsViewState>() {
var callback: Callback? = null
@ -79,6 +81,7 @@ class PublicRoomsController @Inject constructor(private val stringProvider: Stri
private fun buildPublicRoom(publicRoom: PublicRoom, viewState: PublicRoomsViewState) {
publicRoomItem {
avatarRenderer(avatarRenderer)
id(publicRoom.roomId)
roomId(publicRoom.roomId)
avatarUrl(publicRoom.avatarUrl)

View File

@ -47,6 +47,7 @@ class RoomPreviewNoPreviewFragment : VectorBaseFragment() {
@Inject lateinit var errorFormatter: ErrorFormatter
@Inject lateinit var roomPreviewViewModelFactory: RoomPreviewViewModel.Factory
@Inject lateinit var avatarRenderer: AvatarRenderer
private val roomPreviewViewModel: RoomPreviewViewModel by fragmentViewModel()
private val roomPreviewData: RoomPreviewData by args()
@ -65,11 +66,11 @@ class RoomPreviewNoPreviewFragment : VectorBaseFragment() {
super.onViewCreated(view, savedInstanceState)
// Toolbar
AvatarRenderer.render(roomPreviewData.avatarUrl, roomPreviewData.roomId, roomPreviewData.roomName, roomPreviewNoPreviewToolbarAvatar)
avatarRenderer.render(roomPreviewData.avatarUrl, roomPreviewData.roomId, roomPreviewData.roomName, roomPreviewNoPreviewToolbarAvatar)
roomPreviewNoPreviewToolbarTitle.text = roomPreviewData.roomName
// Screen
AvatarRenderer.render(roomPreviewData.avatarUrl, roomPreviewData.roomId, roomPreviewData.roomName, roomPreviewNoPreviewAvatar)
avatarRenderer.render(roomPreviewData.avatarUrl, roomPreviewData.roomId, roomPreviewData.roomName, roomPreviewNoPreviewAvatar)
roomPreviewNoPreviewName.text = roomPreviewData.roomName
roomPreviewNoPreviewTopic.setTextOrHide(roomPreviewData.topic)

View File

@ -55,6 +55,7 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() {
private var testManager: NotificationTroubleshootTestManager? = null
// members
@Inject lateinit var session: Session
@Inject lateinit var bugReporter: BugReporter
override fun getLayoutResId() = R.layout.fragment_settings_notifications_troubleshoot
@ -78,7 +79,7 @@ class VectorSettingsNotificationsTroubleshootFragment : VectorBaseFragment() {
mSummaryButton.setOnClickListener {
BugReporter.openBugReportScreen(activity!!)
bugReporter.openBugReportScreen(activity!!)
}
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.crypto.keysbackup.KeysBackupState
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.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupActivity