Dagger: start handling app dependencies [WIP]

This commit is contained in:
ganfra 2019-06-18 20:00:20 +02:00
parent c2c2d0b21e
commit 9c1f870694
118 changed files with 1562 additions and 1010 deletions

View File

@ -54,7 +54,7 @@ internal class RoomMembers(private val realm: Realm,
} }
return EventEntity return EventEntity
.where(realm, roomId, EventType.STATE_ROOM_MEMBER) .where(realm, roomId, EventType.STATE_ROOM_MEMBER)
.contains(EventEntityFields.CONTENT, displayName) .equalTo(EventEntityFields.CONTENT, displayName)
.distinct(EventEntityFields.STATE_KEY) .distinct(EventEntityFields.STATE_KEY)
.findAll() .findAll()
.size == 1 .size == 1

View File

@ -138,6 +138,7 @@ dependencies {
def big_image_viewer_version = '1.5.6' def big_image_viewer_version = '1.5.6'
def glide_version = '4.9.0' def glide_version = '4.9.0'
def moshi_version = '1.8.0' def moshi_version = '1.8.0'
def daggerVersion = '2.23.1'


implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android")
implementation project(":matrix-sdk-android-rx") implementation project(":matrix-sdk-android-rx")
@ -219,8 +220,10 @@ dependencies {
implementation 'com.github.jaiselrahman:FilePicker:1.2.2' implementation 'com.github.jaiselrahman:FilePicker:1.2.2'


// DI // DI
implementation "org.koin:koin-android:$koin_version" implementation "com.google.dagger:dagger:$daggerVersion"
implementation "org.koin:koin-android-scope:$koin_version" kapt "com.google.dagger:dagger-compiler:$daggerVersion"
compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.4.0'
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.4.0'


// gplay flavor only // gplay flavor only
gplayImplementation 'com.google.firebase:firebase-core:16.0.8' gplayImplementation 'com.google.firebase:firebase-core:16.0.8'

View File

@ -3,9 +3,9 @@ package im.vector.riotredesign
import android.graphics.Typeface import android.graphics.Typeface
import androidx.core.provider.FontsContractCompat import androidx.core.provider.FontsContractCompat
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject



class EmojiCompatFontProvider @Inject constructor(): FontsContractCompat.FontRequestCallback() {
class EmojiCompatFontProvider : FontsContractCompat.FontRequestCallback() {


var typeface: Typeface? = null var typeface: Typeface? = null
set(value) { set(value) {

View File

@ -31,67 +31,56 @@ import com.github.piasy.biv.BigImageViewer
import com.github.piasy.biv.loader.glide.GlideImageLoader import com.github.piasy.biv.loader.glide.GlideImageLoader
import com.jakewharton.threetenabp.AndroidThreeTen import com.jakewharton.threetenabp.AndroidThreeTen
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.core.di.AppModule import im.vector.riotredesign.core.di.HasInjector
import im.vector.riotredesign.core.di.VectorComponent
import im.vector.riotredesign.features.configuration.VectorConfiguration import im.vector.riotredesign.features.configuration.VectorConfiguration
import im.vector.riotredesign.features.crypto.keysbackup.KeysBackupModule
import im.vector.riotredesign.features.home.HomeModule
import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks
import im.vector.riotredesign.features.rageshake.VectorFileLogger import im.vector.riotredesign.features.rageshake.VectorFileLogger
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryModule
import org.koin.android.ext.android.inject
import org.koin.log.EmptyLogger
import org.koin.standalone.StandAloneContext.startKoin
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject




class VectorApplication : Application() { class VectorApplication : Application(), HasInjector<VectorComponent> {


lateinit var appContext: Context lateinit var appContext: Context
//font thread handler //font thread handler
private var mFontThreadHandler: Handler? = null @Inject lateinit var vectorConfiguration: VectorConfiguration

@Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider
val vectorConfiguration: VectorConfiguration by inject() lateinit var vectorComponent: VectorComponent
private var fontThreadHandler: Handler? = null


override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
appContext = this appContext = this

VectorUncaughtExceptionHandler.activate(this) VectorUncaughtExceptionHandler.activate(this)

// Log // Log
VectorFileLogger.init(this) VectorFileLogger.init(this)
Timber.plant(Timber.DebugTree(), VectorFileLogger) Timber.plant(Timber.DebugTree(), VectorFileLogger)

if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Stetho.initializeWithDefaults(this) Stetho.initializeWithDefaults(this)
} }

AndroidThreeTen.init(this) AndroidThreeTen.init(this)
BigImageViewer.initialize(GlideImageLoader.with(applicationContext)) BigImageViewer.initialize(GlideImageLoader.with(applicationContext))
EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
val appModule = AppModule(applicationContext).definition
val homeModule = HomeModule().definition
val roomDirectoryModule = RoomDirectoryModule().definition
val keysBackupModule = KeysBackupModule().definition
val koin = startKoin(listOf(appModule, homeModule, roomDirectoryModule, keysBackupModule), logger = EmptyLogger())
Matrix.getInstance().setApplicationFlavor(BuildConfig.FLAVOR_DESCRIPTION) Matrix.getInstance().setApplicationFlavor(BuildConfig.FLAVOR_DESCRIPTION)
registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks()) registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks())

val fontRequest = FontRequest( val fontRequest = FontRequest(
"com.google.android.gms.fonts", "com.google.android.gms.fonts",
"com.google.android.gms", "com.google.android.gms",
"Noto Color Emoji Compat", "Noto Color Emoji Compat",
R.array.com_google_android_gms_fonts_certs R.array.com_google_android_gms_fonts_certs
) )

FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
// val efp = koin.koinContext.get<EmojiCompatFontProvider>()
FontsContractCompat.requestFont(this, fontRequest, koin.koinContext.get<EmojiCompatFontProvider>(), getFontThreadHandler())

vectorConfiguration.initConfiguration() vectorConfiguration.initConfiguration()
} }


override fun injector(): VectorComponent {
return vectorComponent
}

override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {
super.attachBaseContext(base) super.attachBaseContext(base)
MultiDex.install(this) MultiDex.install(this)
@ -103,12 +92,15 @@ class VectorApplication : Application() {
} }


private fun getFontThreadHandler(): Handler { private fun getFontThreadHandler(): Handler {
if (mFontThreadHandler == null) { return fontThreadHandler ?: createFontThreadHandler().also {
val handlerThread = HandlerThread("fonts") fontThreadHandler = it
handlerThread.start()
mFontThreadHandler = Handler(handlerThread.looper)
} }
return mFontThreadHandler!! }

private fun createFontThreadHandler(): Handler {
val handlerThread = HandlerThread("fonts")
handlerThread.start()
return Handler(handlerThread.looper)
} }


} }

View File

@ -1,107 +0,0 @@
/*
* 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 android.content.Context
import android.content.Context.MODE_PRIVATE
import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.EmojiCompatFontProvider
import im.vector.riotredesign.core.error.ErrorFormatter
import im.vector.riotredesign.core.resources.LocaleProvider
import im.vector.riotredesign.core.resources.StringArrayProvider
import im.vector.riotredesign.core.resources.StringProvider
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.HomeRoomListObservableStore
import im.vector.riotredesign.features.home.group.SelectedGroupStore
import im.vector.riotredesign.features.home.room.list.AlphabeticalRoomComparator
import im.vector.riotredesign.features.home.room.list.ChronologicalRoomComparator
import im.vector.riotredesign.features.navigation.DefaultNavigator
import im.vector.riotredesign.features.navigation.Navigator
import im.vector.riotredesign.features.notifications.NotificationDrawerManager
import org.koin.dsl.module.module

class AppModule(private val context: Context) {

val definition = module {

single {
VectorConfiguration(context)
}

single {
LocaleProvider(context.resources)
}

single {
StringProvider(context.resources)
}

single {
StringArrayProvider(context.resources)
}

single {
context.getSharedPreferences("im.vector.riot", MODE_PRIVATE)
}

single {
SelectedGroupStore()
}

single {
HomeRoomListObservableStore()
}

single {
ChronologicalRoomComparator()
}

single {
AlphabeticalRoomComparator()
}

single {
ErrorFormatter(get())
}

single {
NotificationDrawerManager(context)
}

factory {
Matrix.getInstance().currentSession!!
}

single {
KeyRequestHandler(context, get())
}

single {
IncomingVerificationRequestHandler(context, get())
}

factory {
DefaultNavigator() as Navigator
}

single {
EmojiCompatFontProvider()
}
}
}

View File

@ -0,0 +1,23 @@
/*
* 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 HasInjector<C> {

fun injector(): C

}

View File

@ -0,0 +1,55 @@
/*
* 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 androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import dagger.BindsInstance
import dagger.Component
import im.vector.riotredesign.core.platform.SimpleFragmentActivity
import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
import im.vector.riotredesign.features.home.HomeActivity
import im.vector.riotredesign.features.home.HomeModule
import im.vector.riotredesign.features.home.room.detail.RoomDetailFragment
import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryPickerFragment
import im.vector.riotredesign.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment

@Component(dependencies = [VectorComponent::class], modules = [ViewModelModule::class, HomeModule::class])
@ScreenScope
interface ScreenComponent {

fun viewModelFactory(): ViewModelProvider.Factory

fun inject(activity: SimpleFragmentActivity)

fun inject(activity: HomeActivity)

fun inject(roomDetailFragment: RoomDetailFragment)

fun inject(roomDirectoryPickerFragment: RoomDirectoryPickerFragment)

fun inject(roomPreviewNoPreviewFragment: RoomPreviewNoPreviewFragment)
fun inject(keysBackupSettingsFragment: KeysBackupSettingsFragment)

@Component.Factory
interface Factory {
fun create(vectorComponent: VectorComponent,
@BindsInstance context: AppCompatActivity
): ScreenComponent
}
}

View File

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -14,22 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */


package im.vector.riotredesign.features.crypto.keysbackup package im.vector.riotredesign.core.di


import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupSettingsRecyclerViewController import androidx.appcompat.app.AppCompatActivity
import org.koin.dsl.module.module import dagger.Module
import dagger.Provides
import im.vector.riotredesign.core.glide.GlideApp


class KeysBackupModule { @Module
object ScreenModule {


companion object { @Provides
const val KEYS_BACKUP_SCOPE = "KEYS_BACKUP_SCOPE" @JvmStatic
} fun providesGlideRequests(context: AppCompatActivity) = GlideApp.with(context)


val definition = module(override = true) {

scope(KEYS_BACKUP_SCOPE) {
KeysBackupSettingsRecyclerViewController(get(), get())
}

}
} }

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.riotredesign.core.di;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;

import javax.inject.Scope;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Scope
@Documented
@Retention(RUNTIME)
public @interface ScreenScope {}

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

import com.squareup.inject.assisted.dagger2.AssistedModule
import dagger.Module

/*
@Module(includes = [AssistedInject_VectorAssistedModule::class])
@AssistedModule
class VectorAssistedModule*/

View File

@ -0,0 +1,65 @@
/*
* 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 android.content.Context
import android.content.res.Resources
import dagger.BindsInstance
import dagger.Component
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.session.Session
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.HomeRoomListObservableStore
import im.vector.riotredesign.features.home.group.SelectedGroupStore
import im.vector.riotredesign.features.navigation.Navigator
import im.vector.riotredesign.features.notifications.NotificationDrawerManager
import javax.inject.Singleton

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

fun matrix(): Matrix

fun currentSession(): Session

fun notificationDrawerManager(): NotificationDrawerManager

fun appContext(): Context

fun resources(): Resources

fun vectorConfiguration(): VectorConfiguration

fun navigator(): Navigator

fun homeRoomListObservableStore(): HomeRoomListObservableStore

fun selectedGroupStore(): SelectedGroupStore

fun incomingVerificationRequestHandler(): IncomingVerificationRequestHandler

fun incomingKeyRequestHandler(): KeyRequestHandler

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

}

View File

@ -0,0 +1,67 @@
/*
* 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 android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import android.content.res.Resources
import dagger.Binds
import dagger.Module
import dagger.Provides
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.features.navigation.DefaultNavigator
import im.vector.riotredesign.features.navigation.Navigator

@Module
abstract class VectorModule {

@Module
companion object {

@Provides
@JvmStatic
fun providesResources(context: Context): Resources {
return context.resources
}

@Provides
@JvmStatic
fun providesSharedPreferences(context: Context): SharedPreferences {
return context.getSharedPreferences("im.vector.riot", MODE_PRIVATE)
}

@Provides
@JvmStatic
fun providesMatrix(): Matrix {
return Matrix.getInstance()
}

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

@Binds
abstract fun bindNavigator(navigator: DefaultNavigator): Navigator


}

View File

@ -0,0 +1,50 @@
/*
* 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 androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import javax.inject.Inject
import javax.inject.Provider

/**
* ViewModelFactory which uses Dagger to create the instances.
*/
class VectorViewModelFactory @Inject constructor(
private val creators: @JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>? = creators[modelClass]
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException("Unknown model class: $modelClass")
}
try {
@Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}

View File

@ -0,0 +1,26 @@
/*
* 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 androidx.lifecycle.ViewModel
import dagger.MapKey
import kotlin.reflect.KClass

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)

View File

@ -0,0 +1,150 @@
/*
* 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 androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel
import im.vector.riotredesign.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel_AssistedFactory
import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
import im.vector.riotredesign.features.crypto.verification.SasVerificationViewModel
import im.vector.riotredesign.features.home.HomeActivityViewModel
import im.vector.riotredesign.features.home.HomeActivityViewModel_AssistedFactory
import im.vector.riotredesign.features.home.HomeDetailViewModel
import im.vector.riotredesign.features.home.HomeDetailViewModel_AssistedFactory
import im.vector.riotredesign.features.home.HomeNavigationViewModel
import im.vector.riotredesign.features.home.group.GroupListViewModel
import im.vector.riotredesign.features.home.group.GroupListViewModel_AssistedFactory
import im.vector.riotredesign.features.home.room.detail.RoomDetailViewModel
import im.vector.riotredesign.features.home.room.detail.RoomDetailViewModel_AssistedFactory
import im.vector.riotredesign.features.home.room.detail.composer.TextComposerViewModel
import im.vector.riotredesign.features.home.room.detail.composer.TextComposerViewModel_AssistedFactory
import im.vector.riotredesign.features.home.room.detail.timeline.action.MessageActionsViewModel
import im.vector.riotredesign.features.home.room.detail.timeline.action.MessageActionsViewModel_AssistedFactory
import im.vector.riotredesign.features.home.room.detail.timeline.action.MessageMenuViewModel
import im.vector.riotredesign.features.home.room.detail.timeline.action.MessageMenuViewModel_AssistedFactory
import im.vector.riotredesign.features.home.room.detail.timeline.action.QuickReactionViewModel
import im.vector.riotredesign.features.home.room.detail.timeline.action.QuickReactionViewModel_AssistedFactory
import im.vector.riotredesign.features.home.room.list.RoomListViewModel
import im.vector.riotredesign.features.home.room.list.RoomListViewModel_AssistedFactory
import im.vector.riotredesign.features.reactions.EmojiChooserViewModel
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryNavigationViewModel
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryViewModel
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryViewModel_AssistedFactory
import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryPickerViewModel
import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryPickerViewModel_AssistedFactory
import im.vector.riotredesign.features.roomdirectory.roompreview.RoomPreviewViewModel
import im.vector.riotredesign.features.roomdirectory.roompreview.RoomPreviewViewModel_AssistedFactory
import im.vector.riotredesign.features.workers.signout.SignOutViewModel

@Module
interface ViewModelModule {

@Binds
fun bindViewModelFactory(factory: VectorViewModelFactory): ViewModelProvider.Factory

@Binds
@IntoMap
@ViewModelKey(SignOutViewModel::class)
fun bindSignOutViewModel(viewModel: SignOutViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(EmojiChooserViewModel::class)
fun bindEmojiChooserViewModel(viewModel: EmojiChooserViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(SasVerificationViewModel::class)
fun bindSasVerificationViewModel(viewModel: SasVerificationViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(KeysBackupRestoreFromKeyViewModel::class)
fun bindKeysBackupRestoreFromKeyViewModel(viewModel: KeysBackupRestoreFromKeyViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(KeysBackupRestoreSharedViewModel::class)
fun bindKeysBackupRestoreSharedViewModel(viewModel: KeysBackupRestoreSharedViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(KeysBackupRestoreFromPassphraseViewModel::class)
fun bindKeysBackupRestoreFromPassphraseViewModel(viewModel: KeysBackupRestoreFromPassphraseViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(RoomDirectoryNavigationViewModel::class)
fun bindRoomDirectoryNavigationViewModel(viewModel: RoomDirectoryNavigationViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(HomeNavigationViewModel::class)
fun bindHomeNavigationViewModel(viewModel: HomeNavigationViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(KeysBackupSetupSharedViewModel::class)
fun bindKeysBackupSetupSharedViewModel(viewModel: KeysBackupSetupSharedViewModel): ViewModel

@Binds
fun bind_im_vector_riotredesign_features_home_HomeActivityViewModel(factory: HomeActivityViewModel_AssistedFactory): HomeActivityViewModel.Factory

@Binds
fun bind_im_vector_riotredesign_features_home_room_detail_composer_TextComposerViewModel(factory: TextComposerViewModel_AssistedFactory): TextComposerViewModel.Factory

@Binds
fun bind_im_vector_riotredesign_features_home_room_detail_RoomDetailViewModel(factory: RoomDetailViewModel_AssistedFactory): RoomDetailViewModel.Factory

@Binds
fun bind_im_vector_riotredesign_features_home_room_detail_timeline_action_QuickReactionViewModel(factory: QuickReactionViewModel_AssistedFactory): QuickReactionViewModel.Factory

@Binds
fun bind_im_vector_riotredesign_features_home_room_detail_timeline_action_MessageActionsViewModel(factory: MessageActionsViewModel_AssistedFactory): MessageActionsViewModel.Factory

@Binds
fun bind_im_vector_riotredesign_features_home_room_detail_timeline_action_MessageMenuViewModel(factory: MessageMenuViewModel_AssistedFactory): MessageMenuViewModel.Factory

@Binds
fun bind_im_vector_riotredesign_features_home_room_list_RoomListViewModel(factory: RoomListViewModel_AssistedFactory): RoomListViewModel.Factory

@Binds
fun bind_im_vector_riotredesign_features_home_group_GroupListViewModel(factory: GroupListViewModel_AssistedFactory): GroupListViewModel.Factory

@Binds
fun bind_im_vector_riotredesign_features_home_HomeDetailViewModel(factory: HomeDetailViewModel_AssistedFactory): HomeDetailViewModel.Factory

@Binds
fun bind_im_vector_riotredesign_features_crypto_keysbackup_settings_KeysBackupSettingsViewModel(factory: KeysBackupSettingsViewModel_AssistedFactory): KeysBackupSettingsViewModel.Factory

@Binds
fun bind_im_vector_riotredesign_features_roomdirectory_picker_RoomDirectoryPickerViewModel(factory: RoomDirectoryPickerViewModel_AssistedFactory): RoomDirectoryPickerViewModel.Factory

@Binds
fun bind_im_vector_riotredesign_features_roomdirectory_RoomDirectoryViewModel(factory: RoomDirectoryViewModel_AssistedFactory): RoomDirectoryViewModel.Factory

@Binds
fun bind_im_vector_riotredesign_features_roomdirectory_roompreview_RoomPreviewViewModel(factory: RoomPreviewViewModel_AssistedFactory): RoomPreviewViewModel.Factory

}

View File

@ -19,8 +19,9 @@ package im.vector.riotredesign.core.error
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import javax.inject.Inject


class ErrorFormatter(val stringProvider: StringProvider) { class ErrorFormatter @Inject constructor(val stringProvider: StringProvider) {




fun toHumanReadable(failure: Failure): String { fun toHumanReadable(failure: Failure): String {

View File

@ -33,4 +33,4 @@ fun AppCompatActivity.addFragmentToBackstack(fragment: Fragment, frameId: Int, t


fun AppCompatActivity.hideKeyboard() { fun AppCompatActivity.hideKeyboard() {
currentFocus?.hideKeyboard() currentFocus?.hideKeyboard()
} }

View File

@ -40,4 +40,4 @@ fun Fragment.replaceChildFragment(fragment: Fragment, frameId: Int) {


fun Fragment.addChildFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) { fun Fragment.addChildFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) {
childFragmentManager.inTransaction { replace(frameId, fragment).addToBackStack(tag) } childFragmentManager.inTransaction { replace(frameId, fragment).addToBackStack(tag) }
} }

View File

@ -21,13 +21,12 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.core.utils.LiveEvent
import im.vector.riotredesign.features.configuration.VectorConfiguration import im.vector.riotredesign.features.configuration.VectorConfiguration
import org.koin.standalone.KoinComponent
import org.koin.standalone.inject
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject


class ConfigurationViewModel : ViewModel(), KoinComponent { class ConfigurationViewModel @Inject constructor(

private val vectorConfiguration: VectorConfiguration
private val vectorConfiguration: VectorConfiguration by inject() ) : ViewModel() {


private var currentConfigurationValue: String? = null private var currentConfigurationValue: String? = null


@ -47,7 +46,6 @@ class ConfigurationViewModel : ViewModel(), KoinComponent {
if (newHash != currentConfigurationValue) { if (newHash != currentConfigurationValue) {
Timber.v("Configuration: recreate the Activity") Timber.v("Configuration: recreate the Activity")
currentConfigurationValue = newHash currentConfigurationValue = newHash

_activityRestarter.postValue(LiveEvent(Unit)) _activityRestarter.postValue(LiveEvent(Unit))
} }
} }

View File

@ -15,6 +15,7 @@
*/ */
package im.vector.riotredesign.core.platform package im.vector.riotredesign.core.platform


import android.os.Bundle
import android.view.View import android.view.View
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
@ -25,7 +26,7 @@ import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.hideKeyboard import im.vector.riotredesign.core.extensions.hideKeyboard
import kotlinx.android.synthetic.main.activity.* import kotlinx.android.synthetic.main.activity.*
import org.koin.android.ext.android.get import javax.inject.Inject


/** /**
* Simple activity with a toolbar, a waiting overlay, and a fragment container and a session. * Simple activity with a toolbar, a waiting overlay, and a fragment container and a session.
@ -43,7 +44,13 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() {
@BindView(R.id.waiting_view_status_horizontal_progress) @BindView(R.id.waiting_view_status_horizontal_progress)
lateinit var waitingHorizontalProgress: ProgressBar lateinit var waitingHorizontalProgress: ProgressBar


protected val session = get<Session>() @Inject
lateinit var session: Session

override fun onCreate(savedInstanceState: Bundle?) {
injector().inject(this)
super.onCreate(savedInstanceState)
}


override fun initUiAndData() { override fun initUiAndData() {
configureToolbar(toolbar) configureToolbar(toolbar)

View File

@ -22,35 +22,48 @@ import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.annotation.* import androidx.annotation.AttrRes
import androidx.annotation.LayoutRes
import androidx.annotation.MainThread
import androidx.annotation.MenuRes
import androidx.annotation.Nullable
import androidx.annotation.StringRes
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import butterknife.BindView import butterknife.BindView
import butterknife.ButterKnife import butterknife.ButterKnife
import butterknife.Unbinder import butterknife.Unbinder
import com.airbnb.mvrx.BaseMvRxActivity import com.airbnb.mvrx.BaseMvRxActivity
import com.airbnb.mvrx.MvRxState
import com.bumptech.glide.util.Util import com.bumptech.glide.util.Util
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import im.vector.riotredesign.BuildConfig import im.vector.riotredesign.BuildConfig
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.di.HasInjector
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.core.utils.toast import im.vector.riotredesign.core.utils.toast
import im.vector.riotredesign.features.configuration.VectorConfiguration import im.vector.riotredesign.features.configuration.VectorConfiguration
import im.vector.riotredesign.features.rageshake.BugReportActivity import im.vector.riotredesign.features.rageshake.BugReportActivity
import im.vector.riotredesign.features.rageshake.BugReporter import im.vector.riotredesign.features.rageshake.BugReporter
import im.vector.riotredesign.features.rageshake.RageShake import im.vector.riotredesign.features.rageshake.RageShake
import im.vector.riotredesign.features.roomdirectory.PublicRoomsViewState
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryViewModel
import im.vector.riotredesign.features.themes.ActivityOtherThemes import im.vector.riotredesign.features.themes.ActivityOtherThemes
import im.vector.riotredesign.features.themes.ThemeUtils import im.vector.riotredesign.features.themes.ThemeUtils
import im.vector.riotredesign.receivers.DebugReceiver import im.vector.riotredesign.receivers.DebugReceiver
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import org.koin.android.ext.android.inject
import timber.log.Timber import timber.log.Timber
import javax.inject.Provider
import kotlin.reflect.KClass




abstract class VectorBaseActivity : BaseMvRxActivity() { abstract class VectorBaseActivity : BaseMvRxActivity(), HasInjector<ScreenComponent> {
/* ========================================================================================== /* ==========================================================================================
* UI * UI
* ========================================================================================== */ * ========================================================================================== */
@ -64,8 +77,8 @@ abstract class VectorBaseActivity : BaseMvRxActivity() {
* DATA * DATA
* ========================================================================================== */ * ========================================================================================== */


private val vectorConfiguration: VectorConfiguration by inject() protected lateinit var viewModelFactory: ViewModelProvider.Factory

private lateinit var vectorConfiguration: VectorConfiguration
private lateinit var configurationViewModel: ConfigurationViewModel private lateinit var configurationViewModel: ConfigurationViewModel


private var unBinder: Unbinder? = null private var unBinder: Unbinder? = null
@ -79,6 +92,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity() {
private val restorables = ArrayList<Restorable>() private val restorables = ArrayList<Restorable>()


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


override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {
super.attachBaseContext(vectorConfiguration.getLocalisedContext(base)) super.attachBaseContext(vectorConfiguration.getLocalisedContext(base))
@ -107,10 +121,9 @@ abstract class VectorBaseActivity : BaseMvRxActivity() {
} }


override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

configurationViewModel = ViewModelProviders.of(this, viewModelFactory).get(ConfigurationViewModel::class.java)
configurationViewModel = ViewModelProviders.of(this).get(ConfigurationViewModel::class.java)

configurationViewModel.activityRestarter.observe(this, Observer { configurationViewModel.activityRestarter.observe(this, Observer {
if (!it.hasBeenHandled) { if (!it.hasBeenHandled) {
// Recreate the Activity because configuration has changed // Recreate the Activity because configuration has changed
@ -202,6 +215,10 @@ abstract class VectorBaseActivity : BaseMvRxActivity() {
} }




override fun injector(): ScreenComponent {
return screenComponent
}

/* ========================================================================================== /* ==========================================================================================
* PRIVATE METHODS * PRIVATE METHODS
* ========================================================================================== */ * ========================================================================================== */

View File

@ -18,24 +18,29 @@ package im.vector.riotredesign.core.platform


import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.* import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.ViewModelProvider
import butterknife.ButterKnife import butterknife.ButterKnife
import butterknife.Unbinder import butterknife.Unbinder
import com.airbnb.mvrx.BaseMvRxFragment import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import com.bumptech.glide.util.Util.assertMainThread import com.bumptech.glide.util.Util.assertMainThread
import im.vector.riotredesign.core.di.HasInjector
import im.vector.riotredesign.core.di.ScreenComponent
import im.vector.riotredesign.features.navigation.Navigator import im.vector.riotredesign.features.navigation.Navigator
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import org.koin.android.ext.android.inject
import org.koin.core.parameter.parametersOf
import timber.log.Timber import timber.log.Timber


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


// Butterknife unbinder // Butterknife unbinder
private var mUnBinder: Unbinder? = null private var mUnBinder: Unbinder? = null
@ -48,7 +53,8 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), OnBackPressed {
* Navigator * Navigator
* ========================================================================================== */ * ========================================================================================== */


protected val navigator: Navigator by inject { parametersOf(this) } protected lateinit var viewModelFactory: ViewModelProvider.Factory
protected lateinit var navigator: Navigator


/* ========================================================================================== /* ==========================================================================================
* Life cycle * Life cycle
@ -57,7 +63,6 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), OnBackPressed {
@CallSuper @CallSuper
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

if (getMenuRes() != -1) { if (getMenuRes() != -1) {
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
@ -92,10 +97,13 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), OnBackPressed {


override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()

uiDisposables.dispose() uiDisposables.dispose()
} }


override fun injector(): ScreenComponent {
return vectorBaseActivity.injector()
}

/* ========================================================================================== /* ==========================================================================================
* Restorable * Restorable
* ========================================================================================== */ * ========================================================================================== */

View File

@ -18,14 +18,15 @@


package im.vector.riotredesign.core.resources package im.vector.riotredesign.core.resources


import android.content.Context
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import im.vector.riotredesign.features.themes.ThemeUtils import im.vector.riotredesign.features.themes.ThemeUtils
import javax.inject.Inject


class ColorProvider(private val context: Context) { class ColorProvider @Inject constructor(private val context: AppCompatActivity) {


fun getColor(@ColorRes colorRes: Int): Int { fun getColor(@ColorRes colorRes: Int): Int {
return ContextCompat.getColor(context, colorRes) return ContextCompat.getColor(context, colorRes)

View File

@ -19,8 +19,9 @@ package im.vector.riotredesign.core.resources
import android.content.res.Resources import android.content.res.Resources
import androidx.core.os.ConfigurationCompat import androidx.core.os.ConfigurationCompat
import java.util.* import java.util.*
import javax.inject.Inject


class LocaleProvider(private val resources: Resources) { class LocaleProvider @Inject constructor(private val resources: Resources) {


fun current(): Locale { fun current(): Locale {
return ConfigurationCompat.getLocales(resources.configuration)[0] return ConfigurationCompat.getLocales(resources.configuration)[0]

View File

@ -19,8 +19,9 @@ package im.vector.riotredesign.core.resources
import android.content.res.Resources import android.content.res.Resources
import androidx.annotation.ArrayRes import androidx.annotation.ArrayRes
import androidx.annotation.NonNull import androidx.annotation.NonNull
import javax.inject.Inject


class StringArrayProvider(private val resources: Resources) { class StringArrayProvider @Inject constructor(private val resources: Resources) {


/** /**
* Returns a localized string array from the application's package's * Returns a localized string array from the application's package's

View File

@ -20,8 +20,9 @@ import android.content.res.Resources
import androidx.annotation.NonNull import androidx.annotation.NonNull
import androidx.annotation.PluralsRes import androidx.annotation.PluralsRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import javax.inject.Inject


class StringProvider(private val resources: Resources) { class StringProvider @Inject constructor(private val resources: Resources) {


/** /**
* Returns a localized string from the application's package's * Returns a localized string from the application's package's

View File

@ -28,7 +28,6 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.features.notifications.NotifiableEventResolver import im.vector.riotredesign.features.notifications.NotifiableEventResolver
import im.vector.riotredesign.features.notifications.NotificationUtils import im.vector.riotredesign.features.notifications.NotificationUtils
import org.koin.android.ext.android.inject
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit


@ -162,7 +161,7 @@ class EventStreamServiceX : VectorService() {
val notification = NotificationUtils.buildForegroundServiceNotification(this, R.string.notification_sync_in_progress) val notification = NotificationUtils.buildForegroundServiceNotification(this, R.string.notification_sync_in_progress)
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification) startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
} }
ACTION_GO_TO_FOREGROUND -> { ACTION_GO_TO_FOREGROUND -> {
// Stop foreground notification display // Stop foreground notification display
Timber.i("stopForeground") Timber.i("stopForeground")
stopForeground(true) stopForeground(true)
@ -177,9 +176,9 @@ class EventStreamServiceX : VectorService() {


when (action) { when (action) {
ACTION_START, ACTION_START,
ACTION_GO_TO_FOREGROUND -> ACTION_GO_TO_FOREGROUND ->
when (serviceState) { when (serviceState) {
ServiceState.INIT -> ServiceState.INIT ->
start(false) start(false)
ServiceState.CATCHUP -> ServiceState.CATCHUP ->
// A push has been received before, just change state, to avoid stopping the service when catchup is over // A push has been received before, just change state, to avoid stopping the service when catchup is over
@ -190,12 +189,12 @@ class EventStreamServiceX : VectorService() {
} }
ACTION_STOP, ACTION_STOP,
ACTION_GO_TO_BACKGROUND, ACTION_GO_TO_BACKGROUND,
ACTION_LOGOUT -> ACTION_LOGOUT ->
stop() stop()
ACTION_PUSH_RECEIVED, ACTION_PUSH_RECEIVED,
ACTION_SIMULATED_PUSH_RECEIVED -> ACTION_SIMULATED_PUSH_RECEIVED ->
when (serviceState) { when (serviceState) {
ServiceState.INIT -> ServiceState.INIT ->
start(true) start(true)
ServiceState.CATCHUP -> ServiceState.CATCHUP ->
catchup(true) catchup(true)
@ -203,17 +202,17 @@ class EventStreamServiceX : VectorService() {
// Nothing to do // Nothing to do
Unit Unit
} }
ACTION_PUSH_UPDATE -> pushStatusUpdate() ACTION_PUSH_UPDATE -> pushStatusUpdate()
ACTION_BOOT_COMPLETE -> { ACTION_BOOT_COMPLETE -> {
// No FCM only // No FCM only
mSimulatePushImmediate = true mSimulatePushImmediate = true
stop() stop()
} }
ACTION_APPLICATION_UPGRADE -> { ACTION_APPLICATION_UPGRADE -> {
// FDroid only // FDroid only
catchup(true) catchup(true)
} }
else -> { else -> {
// Should not happen // Should not happen
} }
} }
@ -247,8 +246,8 @@ class EventStreamServiceX : VectorService() {
val pushSimulatorRequest = OneTimeWorkRequestBuilder<PushSimulatorWorker>() val pushSimulatorRequest = OneTimeWorkRequestBuilder<PushSimulatorWorker>()
.setInitialDelay(delay.toLong(), TimeUnit.MILLISECONDS) .setInitialDelay(delay.toLong(), TimeUnit.MILLISECONDS)
.setConstraints(Constraints.Builder() .setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) .setRequiredNetworkType(NetworkType.CONNECTED)
.build()) .build())
.addTag(PUSH_SIMULATOR_REQUEST_TAG) .addTag(PUSH_SIMULATOR_REQUEST_TAG)
.build() .build()



View File

@ -20,8 +20,9 @@ import com.airbnb.epoxy.TypedEpoxyController
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.autocomplete.AutocompleteClickListener import im.vector.riotredesign.features.autocomplete.AutocompleteClickListener
import im.vector.riotredesign.features.command.Command import im.vector.riotredesign.features.command.Command
import javax.inject.Inject


class AutocompleteCommandController(private val stringProvider: StringProvider) : TypedEpoxyController<List<Command>>() { class AutocompleteCommandController @Inject constructor(private val stringProvider: StringProvider) : TypedEpoxyController<List<Command>>() {


var listener: AutocompleteClickListener<Command>? = null var listener: AutocompleteClickListener<Command>? = null



View File

@ -20,9 +20,10 @@ import android.content.Context
import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyController
import im.vector.riotredesign.features.autocomplete.EpoxyAutocompletePresenter import im.vector.riotredesign.features.autocomplete.EpoxyAutocompletePresenter
import im.vector.riotredesign.features.command.Command import im.vector.riotredesign.features.command.Command
import javax.inject.Inject


class AutocompleteCommandPresenter(context: Context, class AutocompleteCommandPresenter @Inject constructor(context: Context,
private val controller: AutocompleteCommandController) : private val controller: AutocompleteCommandController) :
EpoxyAutocompletePresenter<Command>(context) { EpoxyAutocompletePresenter<Command>(context) {


init { init {

View File

@ -18,8 +18,9 @@ package im.vector.riotredesign.features.autocomplete.command


import android.text.Spannable import android.text.Spannable
import com.otaliastudios.autocomplete.AutocompletePolicy import com.otaliastudios.autocomplete.AutocompletePolicy
import javax.inject.Inject


class CommandAutocompletePolicy : AutocompletePolicy { class CommandAutocompletePolicy @Inject constructor() : AutocompletePolicy {


var enabled: Boolean = true var enabled: Boolean = true


@ -37,7 +38,7 @@ class CommandAutocompletePolicy : AutocompletePolicy {
// Only if text which starts with '/' and without space // Only if text which starts with '/' and without space
override fun shouldShowPopup(text: Spannable?, cursorPos: Int): Boolean { override fun shouldShowPopup(text: Spannable?, cursorPos: Int): Boolean {
return enabled && text?.startsWith("/") == true return enabled && text?.startsWith("/") == true
&& !text.contains(" ") && !text.contains(" ")
} }


override fun shouldDismissPopup(text: Spannable?, cursorPos: Int): Boolean { override fun shouldDismissPopup(text: Spannable?, cursorPos: Int): Boolean {

View File

@ -19,8 +19,9 @@ package im.vector.riotredesign.features.autocomplete.user
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotredesign.features.autocomplete.AutocompleteClickListener import im.vector.riotredesign.features.autocomplete.AutocompleteClickListener
import javax.inject.Inject


class AutocompleteUserController : TypedEpoxyController<List<User>>() { class AutocompleteUserController @Inject constructor(): TypedEpoxyController<List<User>>() {


var listener: AutocompleteClickListener<User>? = null var listener: AutocompleteClickListener<User>? = null



View File

@ -22,9 +22,10 @@ import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotredesign.features.autocomplete.EpoxyAutocompletePresenter import im.vector.riotredesign.features.autocomplete.EpoxyAutocompletePresenter
import javax.inject.Inject


class AutocompleteUserPresenter(context: Context, class AutocompleteUserPresenter @Inject constructor(context: Context,
private val controller: AutocompleteUserController private val controller: AutocompleteUserController
) : EpoxyAutocompletePresenter<User>(context) { ) : EpoxyAutocompletePresenter<User>(context) {


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

View File

@ -25,20 +25,22 @@ import im.vector.riotredesign.features.settings.VectorLocale
import im.vector.riotredesign.features.themes.ThemeUtils import im.vector.riotredesign.features.themes.ThemeUtils
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import javax.inject.Inject


/** /**
* Handle locale configuration change, such as theme, font size and locale chosen by the user * Handle locale configuration change, such as theme, font size and locale chosen by the user
*/ */
class VectorConfiguration(private val context: Context) {
class VectorConfiguration @Inject constructor(private val context: Context) {


// TODO Import mLanguageReceiver From Riot? // TODO Import mLanguageReceiver From Riot?
fun onConfigurationChanged(newConfig: Configuration?) { fun onConfigurationChanged(newConfig: Configuration?) {
if (Locale.getDefault().toString() != VectorLocale.applicationLocale.toString()) { if (Locale.getDefault().toString() != VectorLocale.applicationLocale.toString()) {
Timber.v("## onConfigurationChanged() : the locale has been updated to " + Locale.getDefault().toString() Timber.v("## onConfigurationChanged() : the locale has been updated to " + Locale.getDefault().toString()
+ ", restore the expected value " + VectorLocale.applicationLocale.toString()) + ", restore the expected value " + VectorLocale.applicationLocale.toString())
updateApplicationSettings(VectorLocale.applicationLocale, updateApplicationSettings(VectorLocale.applicationLocale,
FontScale.getFontScalePrefValue(context), FontScale.getFontScalePrefValue(context),
ThemeUtils.getApplicationTheme(context)) ThemeUtils.getApplicationTheme(context))
} }
} }


@ -65,8 +67,8 @@ class VectorConfiguration(private val context: Context) {
fun updateApplicationTheme(theme: String) { fun updateApplicationTheme(theme: String) {
ThemeUtils.setApplicationTheme(context, theme) ThemeUtils.setApplicationTheme(context, theme)
updateApplicationSettings(VectorLocale.applicationLocale, updateApplicationSettings(VectorLocale.applicationLocale,
FontScale.getFontScalePrefValue(context), FontScale.getFontScalePrefValue(context),
theme) theme)
} }


/** /**

View File

@ -23,7 +23,6 @@ import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreFromPassphraseFragment import im.vector.fragments.keysbackup.restore.KeysBackupRestoreFromPassphraseFragment
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreSharedViewModel
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.extensions.observeEvent
import im.vector.riotredesign.core.platform.SimpleFragmentActivity import im.vector.riotredesign.core.platform.SimpleFragmentActivity
@ -43,9 +42,8 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {


override fun initUiAndData() { override fun initUiAndData() {
super.initUiAndData() super.initUiAndData()
viewModel = ViewModelProviders.of(this).get(KeysBackupRestoreSharedViewModel::class.java) viewModel = ViewModelProviders.of(this, viewModelFactory).get(KeysBackupRestoreSharedViewModel::class.java)
viewModel.initSession(session) viewModel.initSession(session)

viewModel.keyVersionResult.observe(this, Observer { keyVersion -> viewModel.keyVersionResult.observe(this, Observer { keyVersion ->


if (keyVersion != null && supportFragmentManager.fragments.isEmpty()) { if (keyVersion != null && supportFragmentManager.fragments.isEmpty()) {

View File

@ -27,7 +27,6 @@ import butterknife.BindView
import butterknife.OnClick import butterknife.OnClick
import butterknife.OnTextChanged import butterknife.OnTextChanged
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreSharedViewModel
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.core.utils.startImportTextFromFileIntent import im.vector.riotredesign.core.utils.startImportTextFromFileIntent
@ -53,9 +52,9 @@ class KeysBackupRestoreFromKeyFragment : VectorBaseFragment() {


override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(KeysBackupRestoreFromKeyViewModel::class.java) viewModel = ViewModelProviders.of(this, viewModelFactory).get(KeysBackupRestoreFromKeyViewModel::class.java)
sharedViewModel = activity?.run { sharedViewModel = activity?.run {
ViewModelProviders.of(this).get(KeysBackupRestoreSharedViewModel::class.java) ViewModelProviders.of(this, viewModelFactory).get(KeysBackupRestoreSharedViewModel::class.java)
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")


mKeyTextEdit.setText(viewModel.recoveryCode.value) mKeyTextEdit.setText(viewModel.recoveryCode.value)

View File

@ -18,7 +18,6 @@ package im.vector.riotredesign.features.crypto.keysbackup.restore
import android.content.Context import android.content.Context
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreSharedViewModel
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.listeners.StepProgressListener import im.vector.matrix.android.api.listeners.StepProgressListener
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
@ -28,8 +27,9 @@ import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.WaitingViewData import im.vector.riotredesign.core.platform.WaitingViewData
import im.vector.riotredesign.core.ui.views.KeysBackupBanner import im.vector.riotredesign.core.ui.views.KeysBackupBanner
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject


class KeysBackupRestoreFromKeyViewModel : ViewModel() { class KeysBackupRestoreFromKeyViewModel @Inject constructor() : ViewModel() {


var recoveryCode: MutableLiveData<String> = MutableLiveData() var recoveryCode: MutableLiveData<String> = MutableLiveData()
var recoveryCodeErrorText: MutableLiveData<String> = MutableLiveData() var recoveryCodeErrorText: MutableLiveData<String> = MutableLiveData()

View File

@ -36,6 +36,7 @@ import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.showPassword import im.vector.riotredesign.core.extensions.showPassword
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel


class KeysBackupRestoreFromPassphraseFragment : VectorBaseFragment() { class KeysBackupRestoreFromPassphraseFragment : VectorBaseFragment() {


@ -68,9 +69,9 @@ class KeysBackupRestoreFromPassphraseFragment : VectorBaseFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)


viewModel = ViewModelProviders.of(this).get(KeysBackupRestoreFromPassphraseViewModel::class.java) viewModel = ViewModelProviders.of(this, viewModelFactory).get(KeysBackupRestoreFromPassphraseViewModel::class.java)
sharedViewModel = activity?.run { sharedViewModel = activity?.run {
ViewModelProviders.of(this).get(KeysBackupRestoreSharedViewModel::class.java) ViewModelProviders.of(this, viewModelFactory).get(KeysBackupRestoreSharedViewModel::class.java)
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")





View File

@ -18,7 +18,6 @@ package im.vector.riotredesign.features.crypto.keysbackup.restore
import android.content.Context import android.content.Context
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreSharedViewModel
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.listeners.StepProgressListener import im.vector.matrix.android.api.listeners.StepProgressListener
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
@ -28,8 +27,9 @@ import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.WaitingViewData import im.vector.riotredesign.core.platform.WaitingViewData
import im.vector.riotredesign.core.ui.views.KeysBackupBanner import im.vector.riotredesign.core.ui.views.KeysBackupBanner
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject


class KeysBackupRestoreFromPassphraseViewModel : ViewModel() { class KeysBackupRestoreFromPassphraseViewModel @Inject constructor() : ViewModel() {


var passphrase: MutableLiveData<String> = MutableLiveData() var passphrase: MutableLiveData<String> = MutableLiveData()
var passphraseErrorText: MutableLiveData<String> = MutableLiveData() var passphraseErrorText: MutableLiveData<String> = MutableLiveData()

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package im.vector.fragments.keysbackup.restore package im.vector.riotredesign.features.crypto.keysbackup.restore


import android.content.Context import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
@ -21,13 +21,14 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.WaitingViewData import im.vector.riotredesign.core.platform.WaitingViewData
import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.core.utils.LiveEvent
import javax.inject.Inject


class KeysBackupRestoreSharedViewModel : ViewModel() { class KeysBackupRestoreSharedViewModel @Inject constructor() : ViewModel() {


companion object { companion object {
const val NAVIGATE_TO_RECOVER_WITH_KEY = "NAVIGATE_TO_RECOVER_WITH_KEY" const val NAVIGATE_TO_RECOVER_WITH_KEY = "NAVIGATE_TO_RECOVER_WITH_KEY"

View File

@ -20,7 +20,6 @@ import android.widget.TextView
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import butterknife.BindView import butterknife.BindView
import butterknife.OnClick import butterknife.OnClick
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreSharedViewModel
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.core.utils.LiveEvent
@ -40,7 +39,7 @@ class KeysBackupRestoreSuccessFragment : VectorBaseFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
sharedViewModel = activity?.run { sharedViewModel = activity?.run {
ViewModelProviders.of(this).get(KeysBackupRestoreSharedViewModel::class.java) ViewModelProviders.of(this, viewModelFactory).get(KeysBackupRestoreSharedViewModel::class.java)
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")


sharedViewModel.importKeyResult?.let { sharedViewModel.importKeyResult?.let {

View File

@ -17,16 +17,16 @@ package im.vector.riotredesign.features.crypto.keysbackup.settings


import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.viewModel
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.injector
import im.vector.riotredesign.core.platform.SimpleFragmentActivity import im.vector.riotredesign.core.platform.SimpleFragmentActivity
import im.vector.riotredesign.core.platform.WaitingViewData import im.vector.riotredesign.core.platform.WaitingViewData
import im.vector.riotredesign.features.crypto.keysbackup.KeysBackupModule import javax.inject.Inject
import org.koin.android.scope.ext.android.bindScope
import org.koin.android.scope.ext.android.getOrCreateScope




class KeysBackupManageActivity : SimpleFragmentActivity() { class KeysBackupManageActivity : SimpleFragmentActivity() {
@ -41,12 +41,15 @@ class KeysBackupManageActivity : SimpleFragmentActivity() {
override fun getTitleRes() = R.string.encryption_message_recovery override fun getTitleRes() = R.string.encryption_message_recovery


private val viewModel: KeysBackupSettingsViewModel by viewModel() private val viewModel: KeysBackupSettingsViewModel by viewModel()
@Inject lateinit var keysBackupSettingsViewModelFactory: KeysBackupSettingsViewModel.Factory

override fun onCreate(savedInstanceState: Bundle?) {
injector.inject(this)
super.onCreate(savedInstanceState)
}


override fun initUiAndData() { override fun initUiAndData() {
super.initUiAndData() super.initUiAndData()

bindScope(getOrCreateScope(KeysBackupModule.KEYS_BACKUP_SCOPE))

if (supportFragmentManager.fragments.isEmpty()) { if (supportFragmentManager.fragments.isEmpty()) {
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.replace(R.id.container, KeysBackupSettingsFragment.newInstance()) .replace(R.id.container, KeysBackupSettingsFragment.newInstance())

View File

@ -21,17 +21,15 @@ import androidx.appcompat.app.AlertDialog
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.injector
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.crypto.keysbackup.KeysBackupModule
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreActivity import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupActivity import im.vector.riotredesign.features.crypto.keysbackup.setup.KeysBackupSetupActivity
import kotlinx.android.synthetic.main.fragment_keys_backup_settings.* import kotlinx.android.synthetic.main.fragment_keys_backup_settings.*
import org.koin.android.ext.android.inject import javax.inject.Inject
import org.koin.android.scope.ext.android.bindScope
import org.koin.android.scope.ext.android.getOrCreateScope


class KeysBackupSettingsFragment : VectorBaseFragment(), class KeysBackupSettingsFragment : VectorBaseFragment(),
KeysBackupSettingsRecyclerViewController.Listener { KeysBackupSettingsRecyclerViewController.Listener {


companion object { companion object {
fun newInstance() = KeysBackupSettingsFragment() fun newInstance() = KeysBackupSettingsFragment()
@ -39,14 +37,12 @@ class KeysBackupSettingsFragment : VectorBaseFragment(),


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


private val keysBackupSettingsRecyclerViewController: KeysBackupSettingsRecyclerViewController by inject() @Inject lateinit var keysBackupSettingsRecyclerViewController: KeysBackupSettingsRecyclerViewController

private val viewModel: KeysBackupSettingsViewModel by activityViewModel() private val viewModel: KeysBackupSettingsViewModel by activityViewModel()


override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
injector().inject(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

bindScope(getOrCreateScope(KeysBackupModule.KEYS_BACKUP_SCOPE))
} }


override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@ -17,11 +17,14 @@ package im.vector.riotredesign.features.crypto.keysbackup.settings


import android.view.View import android.view.View
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.* import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.errorWithRetryItem import im.vector.riotredesign.core.epoxy.errorWithRetryItem
import im.vector.riotredesign.core.epoxy.loadingItem import im.vector.riotredesign.core.epoxy.loadingItem
@ -29,9 +32,10 @@ import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.core.ui.list.GenericItem import im.vector.riotredesign.core.ui.list.GenericItem
import im.vector.riotredesign.core.ui.list.genericItem import im.vector.riotredesign.core.ui.list.genericItem
import java.util.* import java.util.*
import javax.inject.Inject


class KeysBackupSettingsRecyclerViewController(val stringProvider: StringProvider, class KeysBackupSettingsRecyclerViewController @Inject constructor(val stringProvider: StringProvider,
val session: Session) : TypedEpoxyController<KeysBackupSettingViewState>() { val session: Session) : TypedEpoxyController<KeysBackupSettingViewState>() {


var listener: Listener? = null var listener: Listener? = null



View File

@ -15,7 +15,15 @@
*/ */
package im.vector.riotredesign.features.crypto.keysbackup.settings package im.vector.riotredesign.features.crypto.keysbackup.settings


import com.airbnb.mvrx.* import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
@ -23,33 +31,37 @@ import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.platform.VectorViewModel
import org.koin.android.ext.android.get




class KeysBackupSettingsViewModel(initialState: KeysBackupSettingViewState, class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
session: Session) : VectorViewModel<KeysBackupSettingViewState>(initialState), session: Session
KeysBackupStateListener { ) : VectorViewModel<KeysBackupSettingViewState>(initialState),
KeysBackupStateListener {

@AssistedInject.Factory
interface Factory {
fun create(initialState: KeysBackupSettingViewState): KeysBackupSettingsViewModel
}


companion object : MvRxViewModelFactory<KeysBackupSettingsViewModel, KeysBackupSettingViewState> { companion object : MvRxViewModelFactory<KeysBackupSettingsViewModel, KeysBackupSettingViewState> {


@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: KeysBackupSettingViewState): KeysBackupSettingsViewModel? { override fun create(viewModelContext: ViewModelContext, state: KeysBackupSettingViewState): KeysBackupSettingsViewModel? {
val session = viewModelContext.activity.get<Session>() val activity: KeysBackupManageActivity = (viewModelContext as ActivityViewModelContext).activity()

return activity.keysBackupSettingsViewModelFactory.create(state)
val firstState = state.copy(
keysBackupState = session.getKeysBackupService().state,
keysBackupVersion = session.getKeysBackupService().keysBackupVersion
)

return KeysBackupSettingsViewModel(firstState, session)
} }
} }


private var keysBackupService: KeysBackupService = session.getKeysBackupService() private var keysBackupService: KeysBackupService = session.getKeysBackupService()


init { init {
setState {
this.copy(
keysBackupState = session.getKeysBackupService().state,
keysBackupVersion = session.getKeysBackupService().keysBackupVersion
)
}
keysBackupService.addListener(this) keysBackupService.addListener(this)

getKeysBackupTrust() getKeysBackupTrust()
} }


@ -90,8 +102,8 @@ class KeysBackupSettingsViewModel(initialState: KeysBackupSettingViewState,
} }


override fun onCleared() { override fun onCleared() {
super.onCleared()
keysBackupService.removeListener(this) keysBackupService.removeListener(this)
super.onCleared()
} }


override fun onStateChange(newState: KeysBackupState) { override fun onStateChange(newState: KeysBackupState) {
@ -142,6 +154,6 @@ class KeysBackupSettingsViewModel(initialState: KeysBackupSettingViewState,
val currentBackupState = keysBackupService.state val currentBackupState = keysBackupService.state


return currentBackupState == KeysBackupState.Unknown return currentBackupState == KeysBackupState.Unknown
|| currentBackupState == KeysBackupState.CheckingBackUpOnHomeserver || currentBackupState == KeysBackupState.CheckingBackUpOnHomeserver
} }
} }

View File

@ -22,7 +22,6 @@ import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import im.vector.fragments.keysbackup.setup.KeysBackupSetupSharedViewModel
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.dialogs.ExportKeysDialog import im.vector.riotredesign.core.dialogs.ExportKeysDialog
import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.extensions.observeEvent
@ -42,7 +41,7 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
.commitNow() .commitNow()
} }


viewModel = ViewModelProviders.of(this).get(KeysBackupSetupSharedViewModel::class.java) viewModel = ViewModelProviders.of(this, viewModelFactory).get(KeysBackupSetupSharedViewModel::class.java)
viewModel.showManualExport.value = intent.getBooleanExtra(EXTRA_SHOW_MANUAL_EXPORT, false) viewModel.showManualExport.value = intent.getBooleanExtra(EXTRA_SHOW_MANUAL_EXPORT, false)
viewModel.initSession(session) viewModel.initSession(session)



View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */


package im.vector.fragments.keysbackup.setup package im.vector.riotredesign.features.crypto.keysbackup.setup


import android.content.Context import android.content.Context
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -30,11 +30,12 @@ import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.WaitingViewData import im.vector.riotredesign.core.platform.WaitingViewData
import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.core.utils.LiveEvent
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject


/** /**
* The shared view model between all fragments. * The shared view model between all fragments.
*/ */
class KeysBackupSetupSharedViewModel : ViewModel() { class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() {


companion object { companion object {
const val NAVIGATE_TO_STEP_2 = "NAVIGATE_TO_STEP_2" const val NAVIGATE_TO_STEP_2 = "NAVIGATE_TO_STEP_2"

View File

@ -24,7 +24,6 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import butterknife.BindView import butterknife.BindView
import butterknife.OnClick import butterknife.OnClick
import im.vector.fragments.keysbackup.setup.KeysBackupSetupSharedViewModel
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.core.utils.LiveEvent
@ -51,7 +50,7 @@ class KeysBackupSetupStep1Fragment : VectorBaseFragment() {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)


viewModel = activity?.run { viewModel = activity?.run {
ViewModelProviders.of(this).get(KeysBackupSetupSharedViewModel::class.java) ViewModelProviders.of(this, viewModelFactory).get(KeysBackupSetupSharedViewModel::class.java)
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")


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

View File

@ -30,7 +30,6 @@ import butterknife.OnClick
import butterknife.OnTextChanged import butterknife.OnTextChanged
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.nulabinc.zxcvbn.Zxcvbn import com.nulabinc.zxcvbn.Zxcvbn
import im.vector.fragments.keysbackup.setup.KeysBackupSetupSharedViewModel
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.showPassword import im.vector.riotredesign.core.extensions.showPassword
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
@ -82,7 +81,7 @@ class KeysBackupSetupStep2Fragment : VectorBaseFragment() {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)


viewModel = activity?.run { viewModel = activity?.run {
ViewModelProviders.of(this).get(KeysBackupSetupSharedViewModel::class.java) ViewModelProviders.of(this, viewModelFactory).get(KeysBackupSetupSharedViewModel::class.java)
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")


viewModel.shouldPromptOnBack = true viewModel.shouldPromptOnBack = true

View File

@ -28,7 +28,6 @@ import androidx.lifecycle.ViewModelProviders
import butterknife.BindView import butterknife.BindView
import butterknife.OnClick import butterknife.OnClick
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import im.vector.fragments.keysbackup.setup.KeysBackupSetupSharedViewModel
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.files.addEntryToDownloadManager import im.vector.riotredesign.core.files.addEntryToDownloadManager
import im.vector.riotredesign.core.files.saveStringToFile import im.vector.riotredesign.core.files.saveStringToFile
@ -58,7 +57,7 @@ class KeysBackupSetupStep3Fragment : VectorBaseFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
viewModel = activity?.run { viewModel = activity?.run {
ViewModelProviders.of(this).get(KeysBackupSetupSharedViewModel::class.java) ViewModelProviders.of(this, viewModelFactory).get(KeysBackupSetupSharedViewModel::class.java)
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")





View File

@ -40,6 +40,8 @@ import timber.log.Timber
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashMap import kotlin.collections.HashMap


@ -50,21 +52,20 @@ import kotlin.collections.HashMap
* If several requests come from same user/device, a single alert is displayed (this alert will accept/reject all request * If several requests come from same user/device, a single alert is displayed (this alert will accept/reject all request
* depending on user action) * depending on user action)
*/ */
class KeyRequestHandler(val context: Context,
val session: Session) @Singleton
class KeyRequestHandler @Inject constructor(val context: Context,
val session: Session)
: RoomKeysRequestListener, : RoomKeysRequestListener,
SasVerificationService.SasVerificationListener { SasVerificationService.SasVerificationListener {


private val alertsToRequests = HashMap<String, ArrayList<IncomingRoomKeyRequest>>() private val alertsToRequests = HashMap<String, ArrayList<IncomingRoomKeyRequest>>()


init { init {
session.getSasVerificationService().addListener(this) session.getSasVerificationService().addListener(this)

session.addRoomKeysRequestListener(this) session.addRoomKeysRequestListener(this)
} }


fun ensureStarted() = Unit

/** /**
* Handle incoming key request. * Handle incoming key request.
* *
@ -194,8 +195,8 @@ class KeyRequestHandler(val context: Context,
Runnable { Runnable {
alert.weakCurrentActivity?.get()?.let { alert.weakCurrentActivity?.get()?.let {
val intent = SASVerificationActivity.outgoingIntent(it, val intent = SASVerificationActivity.outgoingIntent(it,
session.sessionParams.credentials.userId, session.sessionParams.credentials.userId,
userId, deviceId) userId, deviceId)
it.startActivity(intent) it.startActivity(intent)
} }
}, },
@ -246,8 +247,8 @@ class KeyRequestHandler(val context: Context,
val alertMgrUniqueKey = alertManagerId(deviceId!!, userId!!) val alertMgrUniqueKey = alertManagerId(deviceId!!, userId!!)
alertsToRequests[alertMgrUniqueKey]?.removeAll { alertsToRequests[alertMgrUniqueKey]?.removeAll {
it.deviceId == request.deviceId it.deviceId == request.deviceId
&& it.userId == request.userId && it.userId == request.userId
&& it.requestId == request.requestId && it.requestId == request.requestId
} }
if (alertsToRequests[alertMgrUniqueKey]?.isEmpty() == true) { if (alertsToRequests[alertMgrUniqueKey]?.isEmpty() == true) {
PopupAlertManager.cancelAlert(alertMgrUniqueKey) PopupAlertManager.cancelAlert(alertMgrUniqueKey)

View File

@ -23,19 +23,21 @@ import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransactio
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.features.popup.PopupAlertManager import im.vector.riotredesign.features.popup.PopupAlertManager
import javax.inject.Inject
import javax.inject.Singleton


/** /**
* Listens to the VerificationManager and add a new notification when an incoming request is detected. * Listens to the VerificationManager and add a new notification when an incoming request is detected.
*/ */
class IncomingVerificationRequestHandler(val context: Context, @Singleton
private val session: Session) : SasVerificationService.SasVerificationListener { class IncomingVerificationRequestHandler @Inject constructor(val context: Context,
private val session: Session
) : SasVerificationService.SasVerificationListener {


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


fun ensureStarted() = Unit

override fun transactionCreated(tx: SasVerificationTransaction) {} override fun transactionCreated(tx: SasVerificationTransaction) {}


override fun transactionUpdated(tx: SasVerificationTransaction) { override fun transactionUpdated(tx: SasVerificationTransaction) {
@ -44,7 +46,7 @@ class IncomingVerificationRequestHandler(val context: Context,
//Add a notification for every incoming request //Add a notification for every incoming request
val session = Matrix.getInstance().currentSession!! val session = Matrix.getInstance().currentSession!!
val name = session.getUser(tx.otherUserId)?.displayName val name = session.getUser(tx.otherUserId)?.displayName
?: tx.otherUserId ?: tx.otherUserId


val alert = PopupAlertManager.VectorAlert( val alert = PopupAlertManager.VectorAlert(
"kvr_${tx.transactionId}", "kvr_${tx.transactionId}",
@ -54,9 +56,9 @@ class IncomingVerificationRequestHandler(val context: Context,
.apply { .apply {
contentAction = Runnable { contentAction = Runnable {
val intent = SASVerificationActivity.incomingIntent(context, val intent = SASVerificationActivity.incomingIntent(context,
session.sessionParams.credentials.userId, session.sessionParams.credentials.userId,
tx.otherUserId, tx.otherUserId,
tx.transactionId) tx.transactionId)
weakCurrentActivity?.get()?.startActivity(intent) weakCurrentActivity?.get()?.startActivity(intent)
} }
dismissedAction = Runnable { dismissedAction = Runnable {
@ -72,9 +74,9 @@ class IncomingVerificationRequestHandler(val context: Context,
context.getString(R.string.action_open), context.getString(R.string.action_open),
Runnable { Runnable {
val intent = SASVerificationActivity.incomingIntent(context, val intent = SASVerificationActivity.incomingIntent(context,
session.sessionParams.credentials.userId, session.sessionParams.credentials.userId,
tx.otherUserId, tx.otherUserId,
tx.transactionId) tx.transactionId)
weakCurrentActivity?.get()?.startActivity(intent) weakCurrentActivity?.get()?.startActivity(intent)
} }
) )

View File

@ -84,7 +84,7 @@ class SASVerificationActivity : SimpleFragmentActivity() {


override fun initUiAndData() { override fun initUiAndData() {
super.initUiAndData() super.initUiAndData()
viewModel = ViewModelProviders.of(this).get(SasVerificationViewModel::class.java) viewModel = ViewModelProviders.of(this, viewModelFactory).get(SasVerificationViewModel::class.java)
val transactionID: String? = intent.getStringExtra(EXTRA_TRANSACTION_ID) val transactionID: String? = intent.getStringExtra(EXTRA_TRANSACTION_ID)


if (isFirstCreation()) { if (isFirstCreation()) {

View File

@ -53,7 +53,7 @@ class SASVerificationIncomingFragment : VectorBaseFragment() {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)


viewModel = activity?.run { viewModel = activity?.run {
ViewModelProviders.of(this).get(SasVerificationViewModel::class.java) ViewModelProviders.of(this, viewModelFactory).get(SasVerificationViewModel::class.java)
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")


otherUserDisplayNameTextView.text = viewModel.otherUser?.displayName ?: viewModel.otherUserId otherUserDisplayNameTextView.text = viewModel.otherUser?.displayName ?: viewModel.otherUserId

View File

@ -69,7 +69,7 @@ class SASVerificationShortCodeFragment : VectorBaseFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
viewModel = activity?.run { viewModel = activity?.run {
ViewModelProviders.of(this).get(SasVerificationViewModel::class.java) ViewModelProviders.of(this, viewModelFactory).get(SasVerificationViewModel::class.java)
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")





View File

@ -58,11 +58,7 @@ class SASVerificationStartFragment : VectorBaseFragment() {


override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)

viewModel = ViewModelProviders.of(vectorBaseActivity, viewModelFactory).get(SasVerificationViewModel::class.java)
viewModel = activity?.run {
ViewModelProviders.of(this).get(SasVerificationViewModel::class.java)
} ?: throw Exception("Invalid Activity")

viewModel.transactionState.observe(this, Observer { viewModel.transactionState.observe(this, Observer {
val uxState = (viewModel.transaction as? OutgoingSasVerificationRequest)?.uxState val uxState = (viewModel.transaction as? OutgoingSasVerificationRequest)?.uxState
when (uxState) { when (uxState) {
@ -75,14 +71,14 @@ class SASVerificationStartFragment : VectorBaseFragment() {
this.startButtonLoading.animate() this.startButtonLoading.animate()


} }
OutgoingSasVerificationRequest.UxState.SHOW_SAS -> { OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
viewModel.shortCodeReady() viewModel.shortCodeReady()
} }
OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME, OutgoingSasVerificationRequest.UxState.CANCELLED_BY_ME,
OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER -> { OutgoingSasVerificationRequest.UxState.CANCELLED_BY_OTHER -> {
viewModel.navigateCancel() viewModel.navigateCancel()
} }
else -> { else -> {
TransitionManager.beginDelayedTransition(this.rootLayout) TransitionManager.beginDelayedTransition(this.rootLayout)
this.loadingText.isVisible = false this.loadingText.isVisible = false
this.startButton.isVisible = true this.startButton.isVisible = true

View File

@ -35,7 +35,7 @@ class SASVerificationVerifiedFragment : VectorBaseFragment() {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)


viewModel = activity?.run { viewModel = activity?.run {
ViewModelProviders.of(this).get(SasVerificationViewModel::class.java) ViewModelProviders.of(this, viewModelFactory).get(SasVerificationViewModel::class.java)
} ?: throw Exception("Invalid Activity") } ?: throw Exception("Invalid Activity")


} }

View File

@ -25,10 +25,11 @@ import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransactio
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.core.utils.LiveEvent
import javax.inject.Inject




class SasVerificationViewModel : ViewModel(), class SasVerificationViewModel @Inject constructor() : ViewModel(),
SasVerificationService.SasVerificationListener { SasVerificationService.SasVerificationListener {




companion object { companion object {

View File

@ -43,9 +43,7 @@ import im.vector.riotredesign.features.rageshake.BugReporter
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotredesign.features.workers.signout.SignOutUiWorker import im.vector.riotredesign.features.workers.signout.SignOutUiWorker
import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home.*
import org.koin.android.ext.android.inject import javax.inject.Inject
import org.koin.android.scope.ext.android.bindScope
import org.koin.android.scope.ext.android.getOrCreateScope




class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
@ -57,12 +55,13 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {


private val homeActivityViewModel: HomeActivityViewModel by viewModel() private val homeActivityViewModel: HomeActivityViewModel by viewModel()
private lateinit var navigationViewModel: HomeNavigationViewModel private lateinit var navigationViewModel: HomeNavigationViewModel
private val homeNavigator by inject<HomeNavigator>()


@Inject lateinit var homeActivityViewModelFactory: HomeActivityViewModel.Factory
@Inject lateinit var homeNavigator: HomeNavigator
// TODO Move this elsewhere // TODO Move this elsewhere
private val incomingVerificationRequestHandler by inject<IncomingVerificationRequestHandler>() @Inject lateinit var incomingVerificationRequestHandler: IncomingVerificationRequestHandler
// TODO Move this elsewhere // TODO Move this elsewhere
private val keyRequestHandler by inject<KeyRequestHandler>() @Inject lateinit var keyRequestHandler: KeyRequestHandler


private var progress: ProgressDialog? = null private var progress: ProgressDialog? = null


@ -76,10 +75,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {


override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
bindScope(getOrCreateScope(HomeModule.HOME_SCOPE))
homeNavigator.activity = this homeNavigator.activity = this

navigationViewModel = ViewModelProviders.of(this, viewModelFactory).get(HomeNavigationViewModel::class.java)
navigationViewModel = ViewModelProviders.of(this).get(HomeNavigationViewModel::class.java)


drawerLayout.addDrawerListener(drawerListener) drawerLayout.addDrawerListener(drawerListener)
if (isFirstCreation()) { if (isFirstCreation()) {

View File

@ -19,10 +19,12 @@ package im.vector.riotredesign.features.home
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import arrow.core.Option import arrow.core.Option
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import im.vector.matrix.android.api.Matrix import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.group.model.GroupSummary
@ -34,28 +36,31 @@ import im.vector.riotredesign.features.home.group.ALL_COMMUNITIES_GROUP_ID
import im.vector.riotredesign.features.home.group.SelectedGroupStore import im.vector.riotredesign.features.home.group.SelectedGroupStore
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.functions.BiFunction import io.reactivex.functions.BiFunction
import org.koin.android.ext.android.get
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit


data class EmptyState(val isEmpty: Boolean = true) : MvRxState data class EmptyState(val isEmpty: Boolean = true) : MvRxState


class HomeActivityViewModel(state: EmptyState, class HomeActivityViewModel @AssistedInject constructor(@Assisted initialState: EmptyState,
private val session: Session, private val session: Session,
private val selectedGroupStore: SelectedGroupStore, private val selectedGroupStore: SelectedGroupStore,
private val homeRoomListStore: HomeRoomListObservableStore private val homeRoomListStore: HomeRoomListObservableStore
) : VectorViewModel<EmptyState>(state), Session.Listener { ) : VectorViewModel<EmptyState>(initialState), Session.Listener {

@AssistedInject.Factory
interface Factory {
fun create(initialState: EmptyState): HomeActivityViewModel
}


companion object : MvRxViewModelFactory<HomeActivityViewModel, EmptyState> { companion object : MvRxViewModelFactory<HomeActivityViewModel, EmptyState> {


@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: EmptyState): HomeActivityViewModel? { override fun create(viewModelContext: ViewModelContext, state: EmptyState): HomeActivityViewModel? {
val session = Matrix.getInstance().currentSession!! val homeActivity: HomeActivity = (viewModelContext as ActivityViewModelContext).activity()
val selectedGroupStore = viewModelContext.activity.get<SelectedGroupStore>() return homeActivity.homeActivityViewModelFactory.create(state)
val homeRoomListObservableSource = viewModelContext.activity.get<HomeRoomListObservableStore>()
return HomeActivityViewModel(state, session, selectedGroupStore, homeRoomListObservableSource)
} }
} }



private val _isLoading = MutableLiveData<Boolean>() private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> val isLoading: LiveData<Boolean>
get() = _isLoading get() = _isLoading

View File

@ -41,7 +41,7 @@ import im.vector.riotredesign.features.home.room.list.UnreadCounterBadgeView
import im.vector.riotredesign.features.workers.signout.SignOutViewModel import im.vector.riotredesign.features.workers.signout.SignOutViewModel
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_home_detail.* import kotlinx.android.synthetic.main.fragment_home_detail.*
import org.koin.android.ext.android.inject import javax.inject.Inject




@Parcelize @Parcelize
@ -67,7 +67,8 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate {
private val viewModel: HomeDetailViewModel by fragmentViewModel() private val viewModel: HomeDetailViewModel by fragmentViewModel()
private lateinit var navigationViewModel: HomeNavigationViewModel private lateinit var navigationViewModel: HomeNavigationViewModel


private val session by inject<Session>() @Inject lateinit var session: Session
@Inject lateinit var homeDetailViewModelFactory: HomeDetailViewModel.Factory


override fun getLayoutResId(): Int { override fun getLayoutResId(): Int {
return R.layout.fragment_home_detail return R.layout.fragment_home_detail
@ -76,7 +77,7 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
currentDisplayMode = savedInstanceState?.getSerializable(CURRENT_DISPLAY_MODE) as? RoomListFragment.DisplayMode currentDisplayMode = savedInstanceState?.getSerializable(CURRENT_DISPLAY_MODE) as? RoomListFragment.DisplayMode
?: RoomListFragment.DisplayMode.HOME ?: RoomListFragment.DisplayMode.HOME


navigationViewModel = ViewModelProviders.of(requireActivity()).get(HomeNavigationViewModel::class.java) navigationViewModel = ViewModelProviders.of(requireActivity()).get(HomeNavigationViewModel::class.java)


@ -89,7 +90,7 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate {
private fun setupKeysBackupBanner() { private fun setupKeysBackupBanner() {
// Keys backup banner // Keys backup banner
// Use the SignOutViewModel, it observe the keys backup state and this is what we need here // Use the SignOutViewModel, it observe the keys backup state and this is what we need here
val model = ViewModelProviders.of(this).get(SignOutViewModel::class.java) val model = ViewModelProviders.of(this, viewModelFactory).get(SignOutViewModel::class.java)


model.init(session) model.init(session)



View File

@ -16,28 +16,34 @@


package im.vector.riotredesign.features.home package im.vector.riotredesign.features.home


import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.platform.VectorViewModel
import org.koin.android.ext.android.get


/** /**
* View model used to update the home bottom bar notification counts * View model used to update the home bottom bar notification counts
*/ */
class HomeDetailViewModel(initialState: HomeDetailViewState, class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: HomeDetailViewState,
private val session: Session, private val session: Session,
private val homeRoomListStore: HomeRoomListObservableStore) private val homeRoomListStore: HomeRoomListObservableStore)
: VectorViewModel<HomeDetailViewState>(initialState) { : VectorViewModel<HomeDetailViewState>(initialState) {


@AssistedInject.Factory
interface Factory {
fun create(initialState: HomeDetailViewState): HomeDetailViewModel
}

companion object : MvRxViewModelFactory<HomeDetailViewModel, HomeDetailViewState> { companion object : MvRxViewModelFactory<HomeDetailViewModel, HomeDetailViewState> {


@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: HomeDetailViewState): HomeDetailViewModel? { override fun create(viewModelContext: ViewModelContext, state: HomeDetailViewState): HomeDetailViewModel? {
val homeRoomListStore = viewModelContext.activity.get<HomeRoomListObservableStore>() val fragment: HomeDetailFragment = (viewModelContext as FragmentViewModelContext).fragment()
val session = viewModelContext.activity.get<Session>() return fragment.homeDetailViewModelFactory.create(state)
return HomeDetailViewModel(state, session, homeRoomListStore)
} }
} }


@ -65,21 +71,21 @@ class HomeDetailViewModel(initialState: HomeDetailViewState,
.subscribe { list -> .subscribe { list ->
list.let { summaries -> list.let { summaries ->
val peopleNotifications = summaries val peopleNotifications = summaries
.filter { it.isDirect } .filter { it.isDirect }
.map { it.notificationCount } .map { it.notificationCount }
.takeIf { it.isNotEmpty() } .takeIf { it.isNotEmpty() }
?.sumBy { i -> i } ?.sumBy { i -> i }
?: 0 ?: 0
val peopleHasHighlight = summaries val peopleHasHighlight = summaries
.filter { it.isDirect } .filter { it.isDirect }
.any { it.highlightCount > 0 } .any { it.highlightCount > 0 }


val roomsNotifications = summaries val roomsNotifications = summaries
.filter { !it.isDirect } .filter { !it.isDirect }
.map { it.notificationCount } .map { it.notificationCount }
.takeIf { it.isNotEmpty() } .takeIf { it.isNotEmpty() }
?.sumBy { i -> i } ?.sumBy { i -> i }
?: 0 ?: 0
val roomsHasHighlight = summaries val roomsHasHighlight = summaries
.filter { !it.isDirect } .filter { !it.isDirect }
.any { it.highlightCount > 0 } .any { it.highlightCount > 0 }

View File

@ -24,7 +24,7 @@ import im.vector.riotredesign.core.extensions.replaceChildFragment
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.home.group.GroupListFragment import im.vector.riotredesign.features.home.group.GroupListFragment
import kotlinx.android.synthetic.main.fragment_home_drawer.* import kotlinx.android.synthetic.main.fragment_home_drawer.*
import org.koin.android.ext.android.inject import javax.inject.Inject


class HomeDrawerFragment : VectorBaseFragment() { class HomeDrawerFragment : VectorBaseFragment() {


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


val session by inject<Session>() @Inject lateinit var session: Session


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



View File

@ -16,89 +16,20 @@


package im.vector.riotredesign.features.home package im.vector.riotredesign.features.home


import androidx.fragment.app.Fragment import android.os.Handler
import im.vector.riotredesign.core.glide.GlideApp import dagger.Module
import im.vector.riotredesign.core.resources.ColorProvider import dagger.Provides
import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventControllerHandler
import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineAsyncHelper
import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserController
import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter
import im.vector.riotredesign.features.home.group.GroupSummaryController
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotredesign.features.home.room.detail.timeline.factory.*
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotredesign.features.home.room.list.RoomSummaryController
import im.vector.riotredesign.features.html.EventHtmlRenderer
import org.koin.core.parameter.parametersOf
import org.koin.dsl.module.module


class HomeModule { @Module
object HomeModule {


companion object { @Provides
const val HOME_SCOPE = "HOME_SCOPE" @JvmStatic
const val ROOM_DETAIL_SCOPE = "ROOM_DETAIL_SCOPE" @TimelineEventControllerHandler
fun providesTimelineBackgroundHandler(): Handler {
return TimelineAsyncHelper.getBackgroundHandler()
} }


val definition = module {

// Activity scope

scope(HOME_SCOPE) {
HomeNavigator()
}

scope(HOME_SCOPE) {
HomePermalinkHandler(get(), get())
}

// Fragment scopes

factory {
TimelineDateFormatter(get())
}

factory {
NoticeEventFormatter(get())
}

factory { (fragment: Fragment) ->
val colorProvider = ColorProvider(fragment.requireContext())
val timelineDateFormatter = get<TimelineDateFormatter>()
val eventHtmlRenderer = EventHtmlRenderer(GlideApp.with(fragment), fragment.requireContext(), get())
val noticeEventFormatter = get<NoticeEventFormatter>(parameters = { parametersOf(fragment) })
val timelineMediaSizeProvider = TimelineMediaSizeProvider()
val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider,
timelineDateFormatter, eventHtmlRenderer, get(), get())

val timelineItemFactory = TimelineItemFactory(
messageItemFactory = messageItemFactory,
noticeItemFactory = NoticeItemFactory(noticeEventFormatter),
defaultItemFactory = DefaultItemFactory(),
encryptionItemFactory = EncryptionItemFactory(get()),
encryptedItemFactory = EncryptedItemFactory(get())
)
TimelineEventController(timelineDateFormatter, timelineItemFactory, timelineMediaSizeProvider)
}

factory {
RoomSummaryController(get(), get(), get())
}

factory {
GroupSummaryController()
}

scope(ROOM_DETAIL_SCOPE) { (fragment: Fragment) ->
val commandController = AutocompleteCommandController(get())
AutocompleteCommandPresenter(fragment.requireContext(), commandController)
}

scope(ROOM_DETAIL_SCOPE) { (fragment: Fragment) ->
val userController = AutocompleteUserController()
AutocompleteUserPresenter(fragment.requireContext(), userController)
}

}
} }

View File

@ -17,5 +17,6 @@
package im.vector.riotredesign.features.home package im.vector.riotredesign.features.home


import im.vector.riotredesign.core.mvrx.NavigationViewModel import im.vector.riotredesign.core.mvrx.NavigationViewModel
import javax.inject.Inject


class HomeNavigationViewModel : NavigationViewModel<HomeActivity.Navigation>() class HomeNavigationViewModel @Inject constructor() : NavigationViewModel<HomeActivity.Navigation>()

View File

@ -24,11 +24,11 @@ import im.vector.riotredesign.core.extensions.replaceFragment
import im.vector.riotredesign.features.navigation.Navigator import im.vector.riotredesign.features.navigation.Navigator
import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home.*
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject


class HomeNavigator { class HomeNavigator @Inject constructor() {


var activity: HomeActivity? = null var activity: HomeActivity? = null

private var rootRoomId: String? = null private var rootRoomId: String? = null


fun openSelectedGroup(groupSummary: GroupSummary) { fun openSelectedGroup(groupSummary: GroupSummary) {

View File

@ -20,9 +20,10 @@ import android.net.Uri
import im.vector.matrix.android.api.permalinks.PermalinkData import im.vector.matrix.android.api.permalinks.PermalinkData
import im.vector.matrix.android.api.permalinks.PermalinkParser import im.vector.matrix.android.api.permalinks.PermalinkParser
import im.vector.riotredesign.features.navigation.Navigator import im.vector.riotredesign.features.navigation.Navigator
import javax.inject.Inject


class HomePermalinkHandler(private val homeNavigator: HomeNavigator, class HomePermalinkHandler @Inject constructor(private val homeNavigator: HomeNavigator,
private val navigator: Navigator) { private val navigator: Navigator) {


fun launch(deepLink: String?) { fun launch(deepLink: String?) {
val uri = deepLink?.let { Uri.parse(it) } val uri = deepLink?.let { Uri.parse(it) }
@ -35,16 +36,16 @@ class HomePermalinkHandler(private val homeNavigator: HomeNavigator,
} }
val permalinkData = PermalinkParser.parse(deepLink) val permalinkData = PermalinkParser.parse(deepLink)
when (permalinkData) { when (permalinkData) {
is PermalinkData.EventLink -> { is PermalinkData.EventLink -> {
homeNavigator.openRoomDetail(permalinkData.roomIdOrAlias, permalinkData.eventId, navigator) homeNavigator.openRoomDetail(permalinkData.roomIdOrAlias, permalinkData.eventId, navigator)
} }
is PermalinkData.RoomLink -> { is PermalinkData.RoomLink -> {
homeNavigator.openRoomDetail(permalinkData.roomIdOrAlias, null, navigator) homeNavigator.openRoomDetail(permalinkData.roomIdOrAlias, null, navigator)
} }
is PermalinkData.GroupLink -> { is PermalinkData.GroupLink -> {
homeNavigator.openGroupDetail(permalinkData.groupId) homeNavigator.openGroupDetail(permalinkData.groupId)
} }
is PermalinkData.UserLink -> { is PermalinkData.UserLink -> {
homeNavigator.openUserDetail(permalinkData.userId) homeNavigator.openUserDetail(permalinkData.userId)
} }
is PermalinkData.FallbackLink -> { is PermalinkData.FallbackLink -> {

View File

@ -21,8 +21,11 @@ import im.vector.riotredesign.core.utils.RxStore
import im.vector.riotredesign.features.home.room.list.RoomListDisplayModeFilter import im.vector.riotredesign.features.home.room.list.RoomListDisplayModeFilter
import im.vector.riotredesign.features.home.room.list.RoomListFragment import im.vector.riotredesign.features.home.room.list.RoomListFragment
import io.reactivex.Observable import io.reactivex.Observable
import javax.inject.Inject
import javax.inject.Singleton


class HomeRoomListObservableStore : RxStore<List<RoomSummary>>() { @Singleton
class HomeRoomListObservableStore @Inject constructor() : RxStore<List<RoomSummary>>() {


fun observeFilteredBy(displayMode: RoomListFragment.DisplayMode): Observable<List<RoomSummary>> { fun observeFilteredBy(displayMode: RoomListFragment.DisplayMode): Observable<List<RoomSummary>> {
return observe() return observe()

View File

@ -27,7 +27,7 @@ import im.vector.riotredesign.core.platform.StateView
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.home.HomeNavigator import im.vector.riotredesign.features.home.HomeNavigator
import kotlinx.android.synthetic.main.fragment_group_list.* import kotlinx.android.synthetic.main.fragment_group_list.*
import org.koin.android.ext.android.inject import javax.inject.Inject


class GroupListFragment : VectorBaseFragment(), GroupSummaryController.Callback { class GroupListFragment : VectorBaseFragment(), GroupSummaryController.Callback {


@ -38,8 +38,10 @@ class GroupListFragment : VectorBaseFragment(), GroupSummaryController.Callback
} }


private val viewModel: GroupListViewModel by fragmentViewModel() private val viewModel: GroupListViewModel by fragmentViewModel()
private val homeNavigator by inject<HomeNavigator>()
private val groupController by inject<GroupSummaryController>() @Inject lateinit var groupListViewModelFactory: GroupListViewModel.Factory
@Inject lateinit var homeNavigator: HomeNavigator
@Inject lateinit var groupController: GroupSummaryController


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



View File

@ -19,8 +19,11 @@ package im.vector.riotredesign.features.home.group
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import arrow.core.Option import arrow.core.Option
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
@ -28,24 +31,27 @@ import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.core.utils.LiveEvent
import org.koin.android.ext.android.get


const val ALL_COMMUNITIES_GROUP_ID = "ALL_COMMUNITIES_GROUP_ID" const val ALL_COMMUNITIES_GROUP_ID = "ALL_COMMUNITIES_GROUP_ID"


class GroupListViewModel(initialState: GroupListViewState, class GroupListViewModel @AssistedInject constructor(@Assisted initialState: GroupListViewState,
private val selectedGroupHolder: SelectedGroupStore, private val selectedGroupHolder: SelectedGroupStore,
private val session: Session, private val session: Session,
private val stringProvider: StringProvider private val stringProvider: StringProvider
) : VectorViewModel<GroupListViewState>(initialState) { ) : VectorViewModel<GroupListViewState>(initialState) {



@AssistedInject.Factory
interface Factory {
fun create(initialState: GroupListViewState): GroupListViewModel
}

companion object : MvRxViewModelFactory<GroupListViewModel, GroupListViewState> { companion object : MvRxViewModelFactory<GroupListViewModel, GroupListViewState> {


@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: GroupListViewState): GroupListViewModel? { override fun create(viewModelContext: ViewModelContext, state: GroupListViewState): GroupListViewModel? {
val currentSession = viewModelContext.activity.get<Session>() val groupListFragment: GroupListFragment = (viewModelContext as FragmentViewModelContext).fragment()
val selectedGroupHolder = viewModelContext.activity.get<SelectedGroupStore>() return groupListFragment.groupListViewModelFactory.create(state)
val stringProvider = viewModelContext.activity.get<StringProvider>()
return GroupListViewModel(state, selectedGroupHolder, currentSession, stringProvider)
} }
} }



View File

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


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


class GroupSummaryController : TypedEpoxyController<GroupListViewState>() { class GroupSummaryController @Inject constructor(): TypedEpoxyController<GroupListViewState>() {


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



View File

@ -19,5 +19,8 @@ package im.vector.riotredesign.features.home.group
import arrow.core.Option import arrow.core.Option
import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.riotredesign.core.utils.RxStore import im.vector.riotredesign.core.utils.RxStore
import javax.inject.Inject
import javax.inject.Singleton


class SelectedGroupStore : RxStore<Option<GroupSummary>>(Option.empty()) @Singleton
class SelectedGroupStore @Inject constructor() : RxStore<Option<GroupSummary>>(Option.empty())

View File

@ -57,7 +57,13 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotredesign.R import im.vector.riotredesign.R
@ -67,13 +73,19 @@ import im.vector.riotredesign.core.extensions.hideKeyboard
import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.extensions.observeEvent
import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.core.utils.* import im.vector.riotredesign.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA
import im.vector.riotredesign.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA
import im.vector.riotredesign.core.utils.checkPermissions
import im.vector.riotredesign.core.utils.copyToClipboard
import im.vector.riotredesign.core.utils.openCamera
import im.vector.riotredesign.core.utils.shareMedia
import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter
import im.vector.riotredesign.features.autocomplete.command.CommandAutocompletePolicy import im.vector.riotredesign.features.autocomplete.command.CommandAutocompletePolicy
import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter
import im.vector.riotredesign.features.command.Command import im.vector.riotredesign.features.command.Command
import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.HomeModule
import im.vector.riotredesign.features.home.HomePermalinkHandler import im.vector.riotredesign.features.home.HomePermalinkHandler
import im.vector.riotredesign.features.home.getColorFromUserId import im.vector.riotredesign.features.home.getColorFromUserId
import im.vector.riotredesign.features.home.room.detail.composer.TextComposerActions import im.vector.riotredesign.features.home.room.detail.composer.TextComposerActions
@ -99,14 +111,11 @@ import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_detail.* import kotlinx.android.synthetic.main.fragment_room_detail.*
import kotlinx.android.synthetic.main.merge_composer_layout.view.* import kotlinx.android.synthetic.main.merge_composer_layout.view.*
import org.commonmark.parser.Parser import org.commonmark.parser.Parser
import org.koin.android.ext.android.inject
import org.koin.android.scope.ext.android.bindScope
import org.koin.android.scope.ext.android.getOrCreateScope
import org.koin.core.parameter.parametersOf
import ru.noties.markwon.Markwon import ru.noties.markwon.Markwon
import ru.noties.markwon.html.HtmlPlugin import ru.noties.markwon.html.HtmlPlugin
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import javax.inject.Inject




@Parcelize @Parcelize
@ -156,19 +165,21 @@ class RoomDetailFragment :
} }


private val roomDetailArgs: RoomDetailArgs by args() private val roomDetailArgs: RoomDetailArgs by args()
private val session by inject<Session>()
private val glideRequests by lazy { private val glideRequests by lazy {
GlideApp.with(this) GlideApp.with(this)
} }


private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel() private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
private val textComposerViewModel: TextComposerViewModel by fragmentViewModel() private val textComposerViewModel: TextComposerViewModel by fragmentViewModel()
private val timelineEventController: TimelineEventController by inject { parametersOf(this) }
private val commandAutocompletePolicy = CommandAutocompletePolicy()
private val autocompleteCommandPresenter: AutocompleteCommandPresenter by inject { parametersOf(this) }
private val autocompleteUserPresenter: AutocompleteUserPresenter by inject { parametersOf(this) }
private val homePermalinkHandler: HomePermalinkHandler by inject()


@Inject lateinit var session: Session
@Inject lateinit var timelineEventController: TimelineEventController
@Inject lateinit var commandAutocompletePolicy: CommandAutocompletePolicy
@Inject lateinit var autocompleteCommandPresenter: AutocompleteCommandPresenter
@Inject lateinit var autocompleteUserPresenter: AutocompleteUserPresenter
@Inject lateinit var homePermalinkHandler: HomePermalinkHandler
@Inject lateinit var roomDetailViewModelFactory: RoomDetailViewModel.Factory
@Inject lateinit var textComposerViewModelFactory: TextComposerViewModel.Factory
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback


override fun getLayoutResId() = R.layout.fragment_room_detail override fun getLayoutResId() = R.layout.fragment_room_detail
@ -179,9 +190,9 @@ class RoomDetailFragment :
lateinit var composerLayout: TextComposerView lateinit var composerLayout: TextComposerView


override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
injector().inject(this)
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java) actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java)
bindScope(getOrCreateScope(HomeModule.ROOM_DETAIL_SCOPE))
setupToolbar(roomToolbar) setupToolbar(roomToolbar)
setupRecyclerView() setupRecyclerView()
setupComposer() setupComposer()
@ -229,18 +240,18 @@ class RoomDetailFragment :
//TODO this is used at several places, find way to refactor? //TODO this is used at several places, find way to refactor?
val messageContent: MessageContent? = val messageContent: MessageContent? =
event.annotations?.editSummary?.aggregatedContent?.toModel() event.annotations?.editSummary?.aggregatedContent?.toModel()
?: event.root.content.toModel() ?: event.root.content.toModel()
val nonFormattedBody = messageContent?.body ?: "" val nonFormattedBody = messageContent?.body ?: ""
var formattedBody: CharSequence? = null var formattedBody: CharSequence? = null
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
val parser = Parser.builder().build() val parser = Parser.builder().build()
val document = parser.parse(messageContent.formattedBody val document = parser.parse(messageContent.formattedBody
?: messageContent.body) ?: messageContent.body)
formattedBody = Markwon.builder(requireContext()) formattedBody = Markwon.builder(requireContext())
.usePlugin(HtmlPlugin.create()).build().render(document) .usePlugin(HtmlPlugin.create()).build().render(document)
} }
composerLayout.composerRelatedMessageContent.text = formattedBody composerLayout.composerRelatedMessageContent.text = formattedBody
?: nonFormattedBody ?: nonFormattedBody




if (mode == SendMode.EDIT) { if (mode == SendMode.EDIT) {
@ -256,7 +267,7 @@ class RoomDetailFragment :
} }


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


composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text.length) composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text.length)
composerLayout.expand { composerLayout.expand {
@ -279,9 +290,9 @@ class RoomDetailFragment :
REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data) REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data)
REACTION_SELECT_REQUEST_CODE -> { REACTION_SELECT_REQUEST_CODE -> {
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID) val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID)
?: return ?: return
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT) val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT)
?: return ?: return
//TODO check if already reacted with that? //TODO check if already reacted with that?
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId)) roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId))
} }
@ -363,7 +374,7 @@ class RoomDetailFragment :


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


editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
@ -619,7 +630,7 @@ class RoomDetailFragment :
} }
MessageMenuViewModel.ACTION_VIEW_REACTIONS -> { MessageMenuViewModel.ACTION_VIEW_REACTIONS -> {
val messageInformationData = actionData.data as? MessageInformationData val messageInformationData = actionData.data as? MessageInformationData
?: return ?: return
ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId, messageInformationData) ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId, messageInformationData)
.show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS")
} }

View File

@ -19,10 +19,13 @@ package im.vector.riotredesign.features.home.room.detail
import android.text.TextUtils import android.text.TextUtils
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.BehaviorRelay
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.content.ContentAttachmentData
@ -41,14 +44,13 @@ import im.vector.riotredesign.features.home.room.detail.timeline.helper.Timeline
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import org.commonmark.parser.Parser import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer import org.commonmark.renderer.html.HtmlRenderer
import org.koin.android.ext.android.get
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit




class RoomDetailViewModel(initialState: RoomDetailViewState, class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: RoomDetailViewState,
private val session: Session private val session: Session
) : VectorViewModel<RoomDetailViewState>(initialState) { ) : VectorViewModel<RoomDetailViewState>(initialState) {


private val room = session.getRoom(initialState.roomId)!! private val room = session.getRoom(initialState.roomId)!!
@ -62,14 +64,19 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
} }
private val timeline = room.createTimeline(eventId, allowedTypes) private val timeline = room.createTimeline(eventId, allowedTypes)


@AssistedInject.Factory
interface Factory {
fun create(initialState: RoomDetailViewState): RoomDetailViewModel
}

companion object : MvRxViewModelFactory<RoomDetailViewModel, RoomDetailViewState> { companion object : MvRxViewModelFactory<RoomDetailViewModel, RoomDetailViewState> {


const val PAGINATION_COUNT = 50 const val PAGINATION_COUNT = 50


@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomDetailViewState): RoomDetailViewModel? { override fun create(viewModelContext: ViewModelContext, state: RoomDetailViewState): RoomDetailViewModel? {
val currentSession = viewModelContext.activity.get<Session>() val fragment: RoomDetailFragment = (viewModelContext as FragmentViewModelContext).fragment()
return RoomDetailViewModel(state, currentSession) return fragment.roomDetailViewModelFactory.create(state)
} }
} }


@ -201,7 +208,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
} }
SendMode.EDIT -> { SendMode.EDIT -> {
room.editTextMessage(state.selectedEvent?.root?.eventId room.editTextMessage(state.selectedEvent?.root?.eventId
?: "", action.text, action.autoMarkdown) ?: "", action.text, action.autoMarkdown)
setState { setState {
copy( copy(
sendMode = SendMode.REGULAR, sendMode = SendMode.REGULAR,
@ -213,7 +220,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
SendMode.QUOTE -> { SendMode.QUOTE -> {
val messageContent: MessageContent? = val messageContent: MessageContent? =
state.selectedEvent?.annotations?.editSummary?.aggregatedContent?.toModel() state.selectedEvent?.annotations?.editSummary?.aggregatedContent?.toModel()
?: state.selectedEvent?.root?.content.toModel() ?: state.selectedEvent?.root?.content.toModel()
val textMsg = messageContent?.body val textMsg = messageContent?.body


val finalText = legacyRiotQuoteText(textMsg, action.text) val finalText = legacyRiotQuoteText(textMsg, action.text)

View File

@ -17,35 +17,44 @@
package im.vector.riotredesign.features.home.room.detail.composer package im.vector.riotredesign.features.home.room.detail.composer


import arrow.core.Option import arrow.core.Option
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.BehaviorRelay
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.features.home.room.detail.RoomDetailFragment
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.functions.BiFunction import io.reactivex.functions.BiFunction
import org.koin.android.ext.android.get
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit


typealias AutocompleteUserQuery = CharSequence typealias AutocompleteUserQuery = CharSequence


class TextComposerViewModel(initialState: TextComposerViewState, class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: TextComposerViewState,
private val session: Session private val session: Session
) : VectorViewModel<TextComposerViewState>(initialState) { ) : VectorViewModel<TextComposerViewState>(initialState) {



private val room = session.getRoom(initialState.roomId)!! private val room = session.getRoom(initialState.roomId)!!
private val roomId = initialState.roomId private val roomId = initialState.roomId


private val usersQueryObservable = BehaviorRelay.create<Option<AutocompleteUserQuery>>() private val usersQueryObservable = BehaviorRelay.create<Option<AutocompleteUserQuery>>()


@AssistedInject.Factory
interface Factory {
fun create(initialState: TextComposerViewState): TextComposerViewModel
}

companion object : MvRxViewModelFactory<TextComposerViewModel, TextComposerViewState> { companion object : MvRxViewModelFactory<TextComposerViewModel, TextComposerViewState> {


@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: TextComposerViewState): TextComposerViewModel? { override fun create(viewModelContext: ViewModelContext, state: TextComposerViewState): TextComposerViewModel? {
val currentSession = viewModelContext.activity.get<Session>() val fragment : RoomDetailFragment = (viewModelContext as FragmentViewModelContext).fragment()
return TextComposerViewModel(state, currentSession) return fragment.textComposerViewModelFactory.create(state)
} }
} }


@ -80,7 +89,7 @@ class TextComposerViewModel(initialState: TextComposerViewState,
} else { } else {
users.filter { users.filter {
it.displayName?.startsWith(prefix = filter, ignoreCase = true) it.displayName?.startsWith(prefix = filter, ignoreCase = true)
?: false ?: false
} }
} }
} }

View File

@ -25,13 +25,25 @@ import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyController
import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyModel
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
import im.vector.matrix.android.api.session.room.model.message.* 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.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.core.epoxy.LoadingItem_ import im.vector.riotredesign.core.epoxy.LoadingItem_
import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.features.home.room.detail.timeline.factory.TimelineItemFactory import im.vector.riotredesign.features.home.room.detail.timeline.factory.TimelineItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.helper.* 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.item.DaySeparatorItem import im.vector.riotredesign.features.home.room.detail.timeline.item.DaySeparatorItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.DaySeparatorItem_ import im.vector.riotredesign.features.home.room.detail.timeline.item.DaySeparatorItem_
import im.vector.riotredesign.features.home.room.detail.timeline.item.MergedHeaderItem import im.vector.riotredesign.features.home.room.detail.timeline.item.MergedHeaderItem
@ -39,11 +51,12 @@ import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInf
import im.vector.riotredesign.features.media.ImageContentRenderer import im.vector.riotredesign.features.media.ImageContentRenderer
import im.vector.riotredesign.features.media.VideoContentRenderer import im.vector.riotredesign.features.media.VideoContentRenderer
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime
import javax.inject.Inject


class TimelineEventController(private val dateFormatter: TimelineDateFormatter, class TimelineEventController @Inject constructor(private val dateFormatter: TimelineDateFormatter,
private val timelineItemFactory: TimelineItemFactory, private val timelineItemFactory: TimelineItemFactory,
private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
private val backgroundHandler: Handler = TimelineAsyncHelper.getBackgroundHandler() @TimelineEventControllerHandler private val backgroundHandler: Handler
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener { ) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener {


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

View File

@ -0,0 +1,23 @@
/*
* 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.features.home.room.detail.timeline

import javax.inject.Qualifier

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

View File

@ -18,11 +18,12 @@ package im.vector.riotredesign.features.home.room.detail.timeline.action
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.core.utils.LiveEvent
import javax.inject.Inject


/** /**
* Activity shared view model to handle message actions * Activity shared view model to handle message actions
*/ */
class ActionsHandler : ViewModel() { class ActionsHandler @Inject constructor() : ViewModel() {


data class ActionData( data class ActionData(
val actionId: String, val actionId: String,

View File

@ -36,6 +36,7 @@ import im.vector.riotredesign.R
import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
import kotlinx.android.synthetic.main.bottom_sheet_message_actions.* import kotlinx.android.synthetic.main.bottom_sheet_message_actions.*
import javax.inject.Inject


/** /**
* Bottom sheet fragment that shows a message preview with list of contextual actions * Bottom sheet fragment that shows a message preview with list of contextual actions
@ -43,6 +44,7 @@ import kotlinx.android.synthetic.main.bottom_sheet_message_actions.*
*/ */
class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() { class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() {


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


private lateinit var actionHandlerModel: ActionsHandler private lateinit var actionHandlerModel: ActionsHandler

View File

@ -19,6 +19,8 @@ import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
@ -27,17 +29,16 @@ import im.vector.matrix.android.api.session.room.model.message.MessageTextConten
import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
import org.commonmark.parser.Parser import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
import org.koin.android.ext.android.get import im.vector.riotredesign.features.html.EventHtmlRenderer
import org.koin.core.parameter.parametersOf
import ru.noties.markwon.Markwon
import ru.noties.markwon.html.HtmlPlugin
import timber.log.Timber
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*




data class MessageActionState( data class MessageActionState(
val roomId: String,
val eventId: String,
val informationData: MessageInformationData,
val userId: String = "", val userId: String = "",
val senderName: String = "", val senderName: String = "",
val messageBody: CharSequence? = null, val messageBody: CharSequence? = null,
@ -45,64 +46,77 @@ data class MessageActionState(
val showPreview: Boolean = false, val showPreview: Boolean = false,
val canReact: Boolean = false, val canReact: Boolean = false,
val senderAvatarPath: String? = null) val senderAvatarPath: String? = null)
: MvRxState : MvRxState {

constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData)

}


/** /**
* Information related to an event and used to display preview in contextual bottomsheet. * Information related to an event and used to display preview in contextual bottomsheet.
*/ */
class MessageActionsViewModel(initialState: MessageActionState) : VectorViewModel<MessageActionState>(initialState) { class MessageActionsViewModel @AssistedInject constructor(@Assisted initialState: MessageActionState,
private val eventHtmlRenderer: EventHtmlRenderer,
private val session: Session,
private val noticeEventFormatter: NoticeEventFormatter
) : VectorViewModel<MessageActionState>(initialState) {

private val roomId = initialState.roomId
private val eventId = initialState.eventId
private val informationData = initialState.informationData

@AssistedInject.Factory
interface Factory {
fun create(initialState: MessageActionState): MessageActionsViewModel
}


companion object : MvRxViewModelFactory<MessageActionsViewModel, MessageActionState> { companion object : MvRxViewModelFactory<MessageActionsViewModel, MessageActionState> {


override fun initialState(viewModelContext: ViewModelContext): MessageActionState? { override fun create(viewModelContext: ViewModelContext, state: MessageActionState): MessageActionsViewModel? {
val currentSession = viewModelContext.activity.get<Session>() val fragment: MessageActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
val fragment = (viewModelContext as? FragmentViewModelContext)?.fragment return fragment.messageActionViewModelFactory.create(state)
val noticeFormatter = fragment?.get<NoticeEventFormatter>(parameters = { parametersOf(fragment) })
val parcel = viewModelContext.args as TimelineEventFragmentArgs

val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault())

val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
var body: CharSequence? = null
val originTs = event?.root?.originServerTs
return if (event != null) {
when (event.root.type) {
EventType.MESSAGE -> {
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
?: event.root.content.toModel()
body = messageContent?.body
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
val parser = Parser.builder().build()
val document = parser.parse(messageContent.formattedBody
?: messageContent.body)
body = Markwon.builder(viewModelContext.activity)
.usePlugin(HtmlPlugin.create()).build().render(document)
}
}
EventType.STATE_ROOM_NAME,
EventType.STATE_ROOM_TOPIC,
EventType.STATE_ROOM_MEMBER,
EventType.STATE_HISTORY_VISIBILITY,
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
EventType.CALL_ANSWER -> {
body = noticeFormatter?.format(event)
}
}
MessageActionState(
userId = event.root.sender ?: "",
senderName = parcel.informationData.memberName?.toString() ?: "",
messageBody = body,
ts = dateFormat.format(Date(originTs ?: 0)),
showPreview = body != null,
canReact = event.root.type == EventType.MESSAGE && event.sendState.isSent(),
senderAvatarPath = parcel.informationData.avatarUrl
)
} else {
//can this happen?
Timber.e("Failed to retrieve event")
null
}
} }
} }


init {
setState { reduceState(this) }
}

private fun reduceState(state: MessageActionState): MessageActionState {
val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault())
val event = session.getRoom(roomId)?.getTimeLineEvent(eventId) ?: return state
var body: CharSequence? = null
val originTs = event.root.originServerTs
when (event.root.type) {
EventType.MESSAGE -> {
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
?: event.root.content.toModel()
body = messageContent?.body
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
body = eventHtmlRenderer.render(messageContent.formattedBody
?: messageContent.body)
}
}
EventType.STATE_ROOM_NAME,
EventType.STATE_ROOM_TOPIC,
EventType.STATE_ROOM_MEMBER,
EventType.STATE_HISTORY_VISIBILITY,
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
EventType.CALL_ANSWER -> {
body = noticeEventFormatter.format(event)
}
}
return state.copy(
userId = event.root.sender ?: "",
senderName = informationData.memberName?.toString() ?: "",
messageBody = body,
ts = dateFormat.format(Date(originTs ?: 0)),
showPreview = body != null,
canReact = event.root.type == EventType.MESSAGE && event.sendState.isSent(),
senderAvatarPath = informationData.avatarUrl
)
}

} }

View File

@ -29,16 +29,16 @@ import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.features.themes.ThemeUtils import im.vector.riotredesign.features.themes.ThemeUtils
import javax.inject.Inject


/** /**
* Fragment showing the list of available contextual action for a given message. * Fragment showing the list of available contextual action for a given message.
*/ */
class MessageMenuFragment : BaseMvRxFragment() { class MessageMenuFragment : BaseMvRxFragment() {


@Inject lateinit var messageMenuViewModelFactory: MessageMenuViewModel.Factory
private val viewModel: MessageMenuViewModel by fragmentViewModel(MessageMenuViewModel::class) private val viewModel: MessageMenuViewModel by fragmentViewModel(MessageMenuViewModel::class)

private var addSeparators = false private var addSeparators = false

var interactionListener: InteractionListener? = null var interactionListener: InteractionListener? = null


override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->

View File

@ -15,9 +15,12 @@
*/ */
package im.vector.riotredesign.features.home.room.detail.timeline.action package im.vector.riotredesign.features.home.room.detail.timeline.action


import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.events.model.toContent
@ -29,44 +32,76 @@ import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
import org.json.JSONObject import org.json.JSONObject
import org.koin.android.ext.android.get


data class SimpleAction(val uid: String, val titleRes: Int, val iconResId: Int?, val data: Any? = null) data class SimpleAction(val uid: String, val titleRes: Int, val iconResId: Int?, val data: Any? = null)


data class MessageMenuState(val actions: List<SimpleAction> = emptyList()) : MvRxState data class MessageMenuState(
val roomId: String,
val eventId: String,
val informationData: MessageInformationData,
val actions: List<SimpleAction> = emptyList()
) : MvRxState {

constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData)

}


/** /**
* Manages list actions for a given message (copy / paste / forward...) * Manages list actions for a given message (copy / paste / forward...)
*/ */
class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<MessageMenuState>(initialState) { class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: MessageMenuState,
private val session: Session) : VectorViewModel<MessageMenuState>(initialState) {

@AssistedInject.Factory
interface Factory {
fun create(initialState: MessageMenuState): MessageMenuViewModel
}


companion object : MvRxViewModelFactory<MessageMenuViewModel, MessageMenuState> { companion object : MvRxViewModelFactory<MessageMenuViewModel, MessageMenuState> {


override fun initialState(viewModelContext: ViewModelContext): MessageMenuState? { const val ACTION_ADD_REACTION = "add_reaction"
// Args are accessible from the context. const val ACTION_COPY = "copy"
val currentSession = viewModelContext.activity.get<Session>() const val ACTION_EDIT = "edit"
val parcel = viewModelContext.args as TimelineEventFragmentArgs const val ACTION_QUOTE = "quote"
val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId) const val ACTION_REPLY = "reply"
?: return null const val ACTION_SHARE = "share"
const val ACTION_RESEND = "resend"
const val ACTION_DELETE = "delete"
const val VIEW_SOURCE = "VIEW_SOURCE"
const val VIEW_DECRYPTED_SOURCE = "VIEW_DECRYPTED_SOURCE"
const val ACTION_COPY_PERMALINK = "ACTION_COPY_PERMALINK"
const val ACTION_FLAG = "ACTION_FLAG"
const val ACTION_QUICK_REACT = "ACTION_QUICK_REACT"
const val ACTION_VIEW_REACTIONS = "ACTION_VIEW_REACTIONS"


val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel() override fun create(viewModelContext: ViewModelContext, state: MessageMenuState): MessageMenuViewModel? {
?: event.root.content.toModel() val fragment: MessageMenuFragment = (viewModelContext as FragmentViewModelContext).fragment()
val type = messageContent?.type return fragment.messageMenuViewModelFactory.create(state)
}
}


if (!event.sendState.isSent()) { init {
//Resend and Delete setState { reduceState(this) }
return MessageMenuState( }
//TODO
listOf( private fun reduceState(state: MessageMenuState): MessageMenuState {
val event = session.getRoom(state.roomId)?.getTimeLineEvent(state.eventId) ?: return state

val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
?: event.root.content.toModel()
val type = messageContent?.type

val actions = if (!event.sendState.isSent()) {
//Resend and Delete
listOf<SimpleAction>(
// SimpleAction(ACTION_RESEND, R.string.resend, R.drawable.ic_send, event.root.eventId), // SimpleAction(ACTION_RESEND, R.string.resend, R.drawable.ic_send, event.root.eventId),
// //TODO delete icon // //TODO delete icon
// SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId) // SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId)
) )
) } else {
} ArrayList<SimpleAction>().apply {

val actions = ArrayList<SimpleAction>().apply {


if (event.sendState == SendState.SENDING) { if (event.sendState == SendState.SENDING) {
//TODO add cancel? //TODO add cancel?
@ -86,28 +121,28 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
this.add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_reply, event.root.eventId)) this.add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_reply, event.root.eventId))
} }


if (canEdit(event, currentSession.sessionParams.credentials.userId)) { if (canEdit(event, session.sessionParams.credentials.userId)) {
this.add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, event.root.eventId)) this.add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, event.root.eventId))
} }


if (canRedact(event, currentSession.sessionParams.credentials.userId)) { if (canRedact(event, session.sessionParams.credentials.userId)) {
this.add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId)) this.add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId))
} }


if (canQuote(event, messageContent)) { if (canQuote(event, messageContent)) {
this.add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, parcel.eventId)) this.add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, state.eventId))
} }


if (canViewReactions(event)) { if (canViewReactions(event)) {
this.add(SimpleAction(ACTION_VIEW_REACTIONS, R.string.message_view_reaction, R.drawable.ic_view_reactions, parcel.informationData)) this.add(SimpleAction(ACTION_VIEW_REACTIONS, R.string.message_view_reaction, R.drawable.ic_view_reactions, state.informationData))
} }


if (canShare(type)) { if (canShare(type)) {
if (messageContent is MessageImageContent) { if (messageContent is MessageImageContent) {
this.add( this.add(
SimpleAction(ACTION_SHARE, SimpleAction(ACTION_SHARE,
R.string.share, R.drawable.ic_share, R.string.share, R.drawable.ic_share,
currentSession.contentUrlResolver().resolveFullSize(messageContent.url)) session.contentUrlResolver().resolveFullSize(messageContent.url))
) )
} }
//TODO //TODO
@ -123,120 +158,103 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes


this.add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source, JSONObject(event.root.toContent()).toString(4))) this.add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source, JSONObject(event.root.toContent()).toString(4)))
if (event.isEncrypted()) { if (event.isEncrypted()) {
this.add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, parcel.eventId)) this.add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, state.eventId))
} }
this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, parcel.eventId)) this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, state.eventId))


if (currentSession.sessionParams.credentials.userId != event.root.sender && event.root.getClearType() == EventType.MESSAGE) { if (session.sessionParams.credentials.userId != event.root.sender && event.root.getClearType() == EventType.MESSAGE) {
//not sent by me //not sent by me
this.add(SimpleAction(ACTION_FLAG, R.string.report_content, R.drawable.ic_flag, parcel.eventId)) this.add(SimpleAction(ACTION_FLAG, R.string.report_content, R.drawable.ic_flag, state.eventId))
} }
} }

return MessageMenuState(actions)
} }
return state.copy(actions = actions)
}


private fun canReply(event: TimelineEvent, messageContent: MessageContent?): Boolean {
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment private fun canReply(event: TimelineEvent, messageContent: MessageContent?): Boolean {
if (event.root.getClearType() != EventType.MESSAGE) return false //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
return when (messageContent?.type) { if (event.root.getClearType() != EventType.MESSAGE) return false
MessageType.MSGTYPE_TEXT, return when (messageContent?.type) {
MessageType.MSGTYPE_NOTICE, MessageType.MSGTYPE_TEXT,
MessageType.MSGTYPE_EMOTE, MessageType.MSGTYPE_NOTICE,
MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_EMOTE,
MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_IMAGE,
MessageType.MSGTYPE_AUDIO, MessageType.MSGTYPE_VIDEO,
MessageType.MSGTYPE_FILE -> true MessageType.MSGTYPE_AUDIO,
else -> false MessageType.MSGTYPE_FILE -> true
else -> false
}
}

private fun canReact(event: TimelineEvent, messageContent: MessageContent?): Boolean {
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
return event.root.getClearType() == EventType.MESSAGE
}

private fun canQuote(event: TimelineEvent, messageContent: MessageContent?): Boolean {
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
if (event.root.getClearType() != EventType.MESSAGE) return false
return when (messageContent?.type) {
MessageType.MSGTYPE_TEXT,
MessageType.MSGTYPE_NOTICE,
MessageType.MSGTYPE_EMOTE,
MessageType.FORMAT_MATRIX_HTML,
MessageType.MSGTYPE_LOCATION -> {
true
} }
else -> false
} }
}


private fun canReact(event: TimelineEvent, messageContent: MessageContent?): Boolean { private fun canRedact(event: TimelineEvent, myUserId: String): Boolean {
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
return event.root.getClearType() == EventType.MESSAGE if (event.root.getClearType() != EventType.MESSAGE) return false
} //TODO if user is admin or moderator
return event.root.sender == myUserId
}


private fun canQuote(event: TimelineEvent, messageContent: MessageContent?): Boolean { private fun canViewReactions(event: TimelineEvent): Boolean {
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
if (event.root.getClearType() != EventType.MESSAGE) return false if (event.root.getClearType() != EventType.MESSAGE) return false
return when (messageContent?.type) { //TODO if user is admin or moderator
MessageType.MSGTYPE_TEXT, return event.annotations?.reactionsSummary?.isNotEmpty() ?: false
MessageType.MSGTYPE_NOTICE, }
MessageType.MSGTYPE_EMOTE,
MessageType.FORMAT_MATRIX_HTML, private fun canEdit(event: TimelineEvent, myUserId: String): Boolean {
MessageType.MSGTYPE_LOCATION -> { //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
true if (event.root.getClearType() != EventType.MESSAGE) return false
} //TODO if user is admin or moderator
else -> false val messageContent = event.root.content.toModel<MessageContent>()
return event.root.sender == myUserId && (
messageContent?.type == MessageType.MSGTYPE_TEXT
|| messageContent?.type == MessageType.MSGTYPE_EMOTE
)
}


private fun canCopy(type: String?): Boolean {
return when (type) {
MessageType.MSGTYPE_TEXT,
MessageType.MSGTYPE_NOTICE,
MessageType.MSGTYPE_EMOTE,
MessageType.FORMAT_MATRIX_HTML,
MessageType.MSGTYPE_LOCATION -> {
true
} }
else -> false
} }

}
private fun canRedact(event: TimelineEvent, myUserId: String): Boolean {
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
if (event.root.getClearType() != EventType.MESSAGE) return false
//TODO if user is admin or moderator
return event.root.sender == myUserId
}

private fun canViewReactions(event: TimelineEvent): Boolean {
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
if (event.root.getClearType() != EventType.MESSAGE) return false
//TODO if user is admin or moderator
return event.annotations?.reactionsSummary?.isNotEmpty() ?: false
}

private fun canEdit(event: TimelineEvent, myUserId: String): Boolean {
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
if (event.root.getClearType() != EventType.MESSAGE) return false
//TODO if user is admin or moderator
val messageContent = event.root.content.toModel<MessageContent>()
return event.root.sender == myUserId && (
messageContent?.type == MessageType.MSGTYPE_TEXT
|| messageContent?.type == MessageType.MSGTYPE_EMOTE
)
}




private fun canCopy(type: String?): Boolean { private fun canShare(type: String?): Boolean {
return when (type) { return when (type) {
MessageType.MSGTYPE_TEXT, MessageType.MSGTYPE_IMAGE,
MessageType.MSGTYPE_NOTICE, MessageType.MSGTYPE_AUDIO,
MessageType.MSGTYPE_EMOTE, MessageType.MSGTYPE_VIDEO -> {
MessageType.FORMAT_MATRIX_HTML, true
MessageType.MSGTYPE_LOCATION -> {
true
}
else -> false
} }
else -> false
} }


private fun canShare(type: String?): Boolean {
return when (type) {
MessageType.MSGTYPE_IMAGE,
MessageType.MSGTYPE_AUDIO,
MessageType.MSGTYPE_VIDEO -> {
true
}
else -> false
}
}

const val ACTION_ADD_REACTION = "add_reaction"
const val ACTION_COPY = "copy"
const val ACTION_EDIT = "edit"
const val ACTION_QUOTE = "quote"
const val ACTION_REPLY = "reply"
const val ACTION_SHARE = "share"
const val ACTION_RESEND = "resend"
const val ACTION_DELETE = "delete"
const val VIEW_SOURCE = "VIEW_SOURCE"
const val VIEW_DECRYPTED_SOURCE = "VIEW_DECRYPTED_SOURCE"
const val ACTION_COPY_PERMALINK = "ACTION_COPY_PERMALINK"
const val ACTION_FLAG = "ACTION_FLAG"
const val ACTION_QUICK_REACT = "ACTION_QUICK_REACT"
const val ACTION_VIEW_REACTIONS = "ACTION_VIEW_REACTIONS"


} }
} }

View File

@ -31,7 +31,7 @@ import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.riotredesign.EmojiCompatFontProvider import im.vector.riotredesign.EmojiCompatFontProvider
import im.vector.riotredesign.R import im.vector.riotredesign.R
import org.koin.android.ext.android.inject import javax.inject.Inject


/** /**
* Quick Reaction Fragment (agree / like reactions) * Quick Reaction Fragment (agree / like reactions)
@ -57,7 +57,8 @@ class QuickReactionFragment : BaseMvRxFragment() {


var interactionListener: InteractionListener? = null var interactionListener: InteractionListener? = null


val fontProvider by inject<EmojiCompatFontProvider>() @Inject lateinit var fontProvider: EmojiCompatFontProvider
@Inject lateinit var quickReactionViewModelFactory: QuickReactionViewModel.Factory


override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.adapter_item_action_quick_reaction, container, false) val view = inflater.inflate(R.layout.adapter_item_action_quick_reaction, container, false)
@ -68,10 +69,10 @@ class QuickReactionFragment : BaseMvRxFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)


quickReact1Text.text = QuickReactionViewModel.agreePositive quickReact1Text.text = QuickReactionViewModel.AGREE_POSITIVE
quickReact2Text.text = QuickReactionViewModel.agreeNegative quickReact2Text.text = QuickReactionViewModel.AGREE_NEGATIVE
quickReact3Text.text = QuickReactionViewModel.likePositive quickReact3Text.text = QuickReactionViewModel.LIKE_POSITIVE
quickReact4Text.text = QuickReactionViewModel.likeNegative quickReact4Text.text = QuickReactionViewModel.LIKE_NEGATIVE


listOf(quickReact1Text, quickReact2Text, quickReact3Text, quickReact4Text).forEach { listOf(quickReact1Text, quickReact2Text, quickReact3Text, quickReact4Text).forEach {
it.typeface = fontProvider.typeface ?: Typeface.DEFAULT it.typeface = fontProvider.typeface ?: Typeface.DEFAULT
@ -96,7 +97,7 @@ class QuickReactionFragment : BaseMvRxFragment() {
override fun invalidate() = withState(viewModel) { override fun invalidate() = withState(viewModel) {


TransitionManager.beginDelayedTransition(rootLayout) TransitionManager.beginDelayedTransition(rootLayout)
when (it.agreeTrigleState) { when (it.agreeTriggleState) {
TriggleState.NONE -> { TriggleState.NONE -> {
quickReact1Text.alpha = 1f quickReact1Text.alpha = 1f
quickReact2Text.alpha = 1f quickReact2Text.alpha = 1f
@ -130,7 +131,7 @@ class QuickReactionFragment : BaseMvRxFragment() {
if (it.selectionResult != null) { if (it.selectionResult != null) {
val clikedOn = it.selectionResult.first val clikedOn = it.selectionResult.first
interactionListener?.didQuickReactWith(clikedOn, QuickReactionViewModel.getOpposite(clikedOn) interactionListener?.didQuickReactWith(clikedOn, QuickReactionViewModel.getOpposite(clikedOn)
?: "", it.selectionResult.second, it.eventId) ?: "", it.selectionResult.second, it.eventId)
} }
} }



View File

@ -15,12 +15,15 @@
*/ */
package im.vector.riotredesign.features.home.room.detail.timeline.action package im.vector.riotredesign.features.home.room.detail.timeline.action


import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.platform.VectorViewModel
import org.koin.android.ext.android.get import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData


/** /**
* Quick reactions state, it's a toggle with 3rd state * Quick reactions state, it's a toggle with 3rd state
@ -32,33 +35,99 @@ enum class TriggleState {
} }


data class QuickReactionState( data class QuickReactionState(
val agreeTrigleState: TriggleState = TriggleState.NONE, val roomId: String,
val eventId: String,
val informationData: MessageInformationData,
val agreeTriggleState: TriggleState = TriggleState.NONE,
val likeTriggleState: TriggleState = TriggleState.NONE, val likeTriggleState: TriggleState = TriggleState.NONE,
/** Pair of 'clickedOn' and current toggles state*/ /** Pair of 'clickedOn' and current toggles state*/
val selectionResult: Pair<String, List<String>>? = null, val selectionResult: Pair<String, List<String>>? = null
val eventId: String = "") : MvRxState ) : MvRxState {

constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData)

}



/** /**
* Quick reaction view model * Quick reaction view model
*/ */
class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel<QuickReactionState>(initialState) { class QuickReactionViewModel @AssistedInject constructor(@Assisted initialState: QuickReactionState,
private val session: Session) : VectorViewModel<QuickReactionState>(initialState) {


@AssistedInject.Factory
interface Factory {
fun create(initialState: QuickReactionState): QuickReactionViewModel
}

companion object : MvRxViewModelFactory<QuickReactionViewModel, QuickReactionState> {

const val AGREE_POSITIVE = "👍"
const val AGREE_NEGATIVE = "👎"
const val LIKE_POSITIVE = "🙂"
const val LIKE_NEGATIVE = "😔"

fun getOpposite(reaction: String): String? {
return when (reaction) {
AGREE_POSITIVE -> AGREE_NEGATIVE
AGREE_NEGATIVE -> AGREE_POSITIVE
LIKE_POSITIVE -> LIKE_NEGATIVE
LIKE_NEGATIVE -> LIKE_POSITIVE
else -> null
}
}

override fun create(viewModelContext: ViewModelContext, state: QuickReactionState): QuickReactionViewModel? {
val fragment: QuickReactionFragment = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.quickReactionViewModelFactory.create(state)
}
}

init {
setState { reduceState(this) }
}

private fun reduceState(state: QuickReactionState): QuickReactionState {
val event = session.getRoom(state.roomId)?.getTimeLineEvent(state.eventId) ?: return state
var agreeTriggle: TriggleState = TriggleState.NONE
var likeTriggle: TriggleState = TriggleState.NONE
event.annotations?.reactionsSummary?.forEach {
//it.addedByMe
if (it.addedByMe) {
if (AGREE_POSITIVE == it.key) {
agreeTriggle = TriggleState.FIRST
} else if (AGREE_NEGATIVE == it.key) {
agreeTriggle = TriggleState.SECOND
}

if (LIKE_POSITIVE == it.key) {
likeTriggle = TriggleState.FIRST
} else if (LIKE_NEGATIVE == it.key) {
likeTriggle = TriggleState.SECOND
}
}
}
return state.copy(
agreeTriggleState = agreeTriggle,
likeTriggleState = likeTriggle
)
}


fun toggleAgree(isFirst: Boolean) = withState { fun toggleAgree(isFirst: Boolean) = withState {
if (isFirst) { if (isFirst) {
setState { setState {
val newTriggle = if (it.agreeTrigleState == TriggleState.FIRST) TriggleState.NONE else TriggleState.FIRST val newTriggle = if (it.agreeTriggleState == TriggleState.FIRST) TriggleState.NONE else TriggleState.FIRST
copy( copy(
agreeTrigleState = newTriggle, agreeTriggleState = newTriggle,
selectionResult = Pair(agreePositive, getReactions(this, newTriggle, null)) selectionResult = Pair(AGREE_POSITIVE, getReactions(this, newTriggle, null))
) )
} }
} else { } else {
setState { setState {
val newTriggle = if (it.agreeTrigleState == TriggleState.SECOND) TriggleState.NONE else TriggleState.SECOND val newTriggle = if (it.agreeTriggleState == TriggleState.SECOND) TriggleState.NONE else TriggleState.SECOND
copy( copy(
agreeTrigleState = agreeTrigleState, agreeTriggleState = agreeTriggleState,
selectionResult = Pair(agreeNegative, getReactions(this, newTriggle, null)) selectionResult = Pair(AGREE_NEGATIVE, getReactions(this, newTriggle, null))
) )
} }
} }
@ -70,7 +139,7 @@ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel
val newTriggle = if (it.likeTriggleState == TriggleState.FIRST) TriggleState.NONE else TriggleState.FIRST val newTriggle = if (it.likeTriggleState == TriggleState.FIRST) TriggleState.NONE else TriggleState.FIRST
copy( copy(
likeTriggleState = newTriggle, likeTriggleState = newTriggle,
selectionResult = Pair(likePositive, getReactions(this, null, newTriggle)) selectionResult = Pair(LIKE_POSITIVE, getReactions(this, null, newTriggle))
) )
} }
} else { } else {
@ -78,7 +147,7 @@ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel
val newTriggle = if (it.likeTriggleState == TriggleState.SECOND) TriggleState.NONE else TriggleState.SECOND val newTriggle = if (it.likeTriggleState == TriggleState.SECOND) TriggleState.NONE else TriggleState.SECOND
copy( copy(
likeTriggleState = newTriggle, likeTriggleState = newTriggle,
selectionResult = Pair(likeNegative, getReactions(this, null, newTriggle)) selectionResult = Pair(LIKE_NEGATIVE, getReactions(this, null, newTriggle))
) )
} }
} }
@ -87,64 +156,17 @@ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel
private fun getReactions(state: QuickReactionState, newState1: TriggleState?, newState2: TriggleState?): List<String> { private fun getReactions(state: QuickReactionState, newState1: TriggleState?, newState2: TriggleState?): List<String> {
return ArrayList<String>(4).apply { return ArrayList<String>(4).apply {
when (newState2 ?: state.likeTriggleState) { when (newState2 ?: state.likeTriggleState) {
TriggleState.FIRST -> add(likePositive) TriggleState.FIRST -> add(LIKE_POSITIVE)
TriggleState.SECOND -> add(likeNegative) TriggleState.SECOND -> add(LIKE_NEGATIVE)
else -> { else -> {
} }
} }
when (newState1 ?: state.agreeTrigleState) { when (newState1 ?: state.agreeTriggleState) {
TriggleState.FIRST -> add(agreePositive) TriggleState.FIRST -> add(AGREE_POSITIVE)
TriggleState.SECOND -> add(agreeNegative) TriggleState.SECOND -> add(AGREE_NEGATIVE)
else -> { else -> {
} }
} }
} }
} }


companion object : MvRxViewModelFactory<QuickReactionViewModel, QuickReactionState> {

val agreePositive = "👍"
val agreeNegative = "👎"
val likePositive = "🙂"
val likeNegative = "😔"

fun getOpposite(reaction: String): String? {
return when (reaction) {
agreePositive -> agreeNegative
agreeNegative -> agreePositive
likePositive -> likeNegative
likeNegative -> likePositive
else -> null
}
}

override fun initialState(viewModelContext: ViewModelContext): QuickReactionState? {
// Args are accessible from the context.
// val foo = vieWModelContext.args<MyArgs>.foo
val currentSession = viewModelContext.activity.get<Session>()
val parcel = viewModelContext.args as TimelineEventFragmentArgs
val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
?: return null
var agreeTriggle: TriggleState = TriggleState.NONE
var likeTriggle: TriggleState = TriggleState.NONE
event.annotations?.reactionsSummary?.forEach {
//it.addedByMe
if (it.addedByMe) {
if (agreePositive == it.key) {
agreeTriggle = TriggleState.FIRST
} else if (agreeNegative == it.key) {
agreeTriggle = TriggleState.SECOND
}

if (likePositive == it.key) {
likeTriggle = TriggleState.FIRST
} else if (likeNegative == it.key) {
likeTriggle = TriggleState.SECOND
}
}
}
return QuickReactionState(agreeTriggle, likeTriggle, null, event.root.eventId ?: "")
}
}
} }

View File

@ -19,8 +19,9 @@ package im.vector.riotredesign.features.home.room.detail.timeline.factory
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.features.home.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.DefaultItem_ import im.vector.riotredesign.features.home.room.detail.timeline.item.DefaultItem_
import javax.inject.Inject


class DefaultItemFactory { class DefaultItemFactory @Inject constructor(){


fun create(event: TimelineEvent, exception: Exception? = null): DefaultItem? { fun create(event: TimelineEvent, exception: Exception? = null): DefaultItem? {
val text = if (exception == null) { val text = if (exception == null) {

View File

@ -30,9 +30,10 @@ import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAv
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
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


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


fun create(timelineEvent: TimelineEvent): VectorEpoxyModel<*>? { fun create(timelineEvent: TimelineEvent): VectorEpoxyModel<*>? {
return when { return when {

View File

@ -28,8 +28,9 @@ import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderNa
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_
import javax.inject.Inject


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


fun create(event: TimelineEvent): NoticeItem? { fun create(event: TimelineEvent): NoticeItem? {
val text = buildNoticeText(event.root, event.senderName) ?: return null val text = buildNoticeText(event.root, event.senderName) ?: return null
@ -52,7 +53,7 @@ class EncryptionItemFactory(private val stringProvider: StringProvider) {
val content = event.content.toModel<EncryptionEventContent>() ?: return null val content = event.content.toModel<EncryptionEventContent>() ?: return null
stringProvider.getString(R.string.notice_end_to_end, senderName, content.algorithm) stringProvider.getString(R.string.notice_end_to_end, senderName, content.algorithm)
} }
else -> null else -> null
} }


} }

View File

@ -29,7 +29,14 @@ import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.EmojiCompatFontProvider import im.vector.riotredesign.EmojiCompatFontProvider
@ -44,18 +51,32 @@ import im.vector.riotredesign.features.home.getColorFromUserId
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotredesign.features.home.room.detail.timeline.item.* 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.html.EventHtmlRenderer import im.vector.riotredesign.features.html.EventHtmlRenderer
import im.vector.riotredesign.features.media.ImageContentRenderer import im.vector.riotredesign.features.media.ImageContentRenderer
import im.vector.riotredesign.features.media.VideoContentRenderer import im.vector.riotredesign.features.media.VideoContentRenderer
import me.gujun.android.span.span import me.gujun.android.span.span
import javax.inject.Inject


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


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


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


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


if (event.root.unsignedData?.redactedEvent != null) { if (event.root.unsignedData?.redactedEvent != null) {
@ -104,9 +125,9 @@ class MessageItemFactory(private val colorProvider: ColorProvider,


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


if (messageContent.relatesTo?.type == RelationType.REPLACE) { if (messageContent.relatesTo?.type == RelationType.REPLACE) {
// ignore replace event, the targeted id is already edited // ignore replace event, the targeted id is already edited
@ -116,16 +137,16 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
// val ev = all.toModel<Event>() // val ev = all.toModel<Event>()
return when (messageContent) { return when (messageContent) {
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, is MessageEmoteContent -> buildEmoteMessageItem(messageContent,
informationData, informationData,
hasBeenEdited, hasBeenEdited,
event.annotations?.editSummary, event.annotations?.editSummary,
callback) callback)
is MessageTextContent -> buildTextMessageItem(event.sendState, is MessageTextContent -> buildTextMessageItem(event.sendState,
messageContent, messageContent,
informationData, informationData,
hasBeenEdited, hasBeenEdited,
event.annotations?.editSummary, event.annotations?.editSummary,
callback callback
) )
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback) is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback)
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback) is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback)
@ -156,7 +177,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }


@ -176,7 +197,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
.clickListener( .clickListener(
DebouncedClickListener(View.OnClickListener { _ -> DebouncedClickListener(View.OnClickListener { _ ->
@ -221,7 +242,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }


@ -259,7 +280,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) } .clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) }
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }


@ -296,7 +317,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }


@ -328,9 +349,9 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
//nop //nop
} }
}, },
editStart, editStart,
editEnd, editEnd,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE) Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
return spannable return spannable
} }


@ -362,7 +383,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }


@ -395,7 +416,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }



View File

@ -24,8 +24,9 @@ import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderNa
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_
import javax.inject.Inject


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


fun create(event: TimelineEvent, fun create(event: TimelineEvent,
callback: TimelineEventController.Callback?): NoticeItem? { callback: TimelineEventController.Callback?): NoticeItem? {

View File

@ -28,12 +28,13 @@ import im.vector.riotredesign.features.home.room.detail.timeline.helper.Timeline
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData 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 timber.log.Timber import timber.log.Timber
import javax.inject.Inject


class TimelineItemFactory(private val messageItemFactory: MessageItemFactory, class TimelineItemFactory @Inject constructor(private val messageItemFactory: MessageItemFactory,
private val encryptionItemFactory: EncryptionItemFactory, private val encryptionItemFactory: EncryptionItemFactory,
private val encryptedItemFactory: EncryptedItemFactory, private val encryptedItemFactory: EncryptedItemFactory,
private val noticeItemFactory: NoticeItemFactory, private val noticeItemFactory: NoticeItemFactory,
private val defaultItemFactory: DefaultItemFactory) { private val defaultItemFactory: DefaultItemFactory) {


fun create(event: TimelineEvent, fun create(event: TimelineEvent,
nextEvent: TimelineEvent?, nextEvent: TimelineEvent?,
@ -64,22 +65,23 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
//These are just for debug to display hidden event, they should be filtered out in normal mode //These are just for debug to display hidden event, they should be filtered out in normal mode
if (TimelineDisplayableEvents.DEBUG_HIDDEN_EVENT) { if (TimelineDisplayableEvents.DEBUG_HIDDEN_EVENT) {
val informationData = MessageInformationData(eventId = event.root.eventId val informationData = MessageInformationData(eventId = event.root.eventId
?: "?", ?: "?",
senderId = event.root.sender ?: "", senderId = event.root.sender
sendState = event.sendState, ?: "",
time = "", sendState = event.sendState,
avatarUrl = null, time = "",
memberName = "", avatarUrl = null,
showInformation = false memberName = "",
showInformation = false
) )
val messageContent = event.root.content.toModel<MessageContent>() val messageContent = event.root.content.toModel<MessageContent>()
?: MessageDefaultContent("", "", null, null) ?: MessageDefaultContent("", "", null, null)
MessageTextItem_() MessageTextItem_()
.informationData(informationData) .informationData(informationData)
.message("{ \"type\": ${event.root.type} }") .message("{ \"type\": ${event.root.type} }")
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} else { } else {
Timber.w("Ignored event (type: ${event.root.type}") Timber.w("Ignored event (type: ${event.root.type}")

View File

@ -20,15 +20,21 @@ import android.text.TextUtils
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.* import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomNameContent
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject


class NoticeEventFormatter(private val stringProvider: StringProvider) { class NoticeEventFormatter @Inject constructor(private val stringProvider: StringProvider) {


fun format(timelineEvent: TimelineEvent): CharSequence? { fun format(timelineEvent: TimelineEvent): CharSequence? {
return when (val type = timelineEvent.root.getClearType()) { return when (val type = timelineEvent.root.getClearType()) {
@ -66,7 +72,7 @@ class NoticeEventFormatter(private val stringProvider: StringProvider) {


private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? { private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? {
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility
?: return null ?: return null


val formattedVisibility = when (historyVisibility) { val formattedVisibility = when (historyVisibility) {
RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared) RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared)
@ -116,7 +122,7 @@ class NoticeEventFormatter(private val stringProvider: StringProvider) {
stringProvider.getString(R.string.notice_display_name_removed, event.sender, prevEventContent?.displayName) stringProvider.getString(R.string.notice_display_name_removed, event.sender, prevEventContent?.displayName)
else -> else ->
stringProvider.getString(R.string.notice_display_name_changed_from, stringProvider.getString(R.string.notice_display_name_changed_from,
event.sender, prevEventContent?.displayName, eventContent?.displayName) event.sender, prevEventContent?.displayName, eventContent?.displayName)
} }
displayText.append(displayNameText) displayText.append(displayNameText)
} }
@ -143,7 +149,7 @@ class NoticeEventFormatter(private val stringProvider: StringProvider) {
when { when {
eventContent.thirdPartyInvite != null -> eventContent.thirdPartyInvite != null ->
stringProvider.getString(R.string.notice_room_third_party_registered_invite, stringProvider.getString(R.string.notice_room_third_party_registered_invite,
targetDisplayName, eventContent.thirdPartyInvite?.displayName) targetDisplayName, eventContent.thirdPartyInvite?.displayName)
TextUtils.equals(event.stateKey, selfUserId) -> TextUtils.equals(event.stateKey, selfUserId) ->
stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName) stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName)
event.stateKey.isNullOrEmpty() -> event.stateKey.isNullOrEmpty() ->

View File

@ -19,8 +19,10 @@ package im.vector.riotredesign.features.home.room.detail.timeline.helper
import im.vector.riotredesign.core.resources.LocaleProvider import im.vector.riotredesign.core.resources.LocaleProvider
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime
import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.DateTimeFormatter
import javax.inject.Inject


class TimelineDateFormatter(private val localeProvider: LocaleProvider) {
class TimelineDateFormatter @Inject constructor (private val localeProvider: LocaleProvider) {


private val messageHourFormatter by lazy { private val messageHourFormatter by lazy {
DateTimeFormatter.ofPattern("H:mm", localeProvider.current()) DateTimeFormatter.ofPattern("H:mm", localeProvider.current())

View File

@ -17,8 +17,9 @@
package im.vector.riotredesign.features.home.room.detail.timeline.helper package im.vector.riotredesign.features.home.room.detail.timeline.helper


import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import javax.inject.Inject


class TimelineMediaSizeProvider { class TimelineMediaSizeProvider @Inject constructor() {


lateinit var recyclerView: RecyclerView lateinit var recyclerView: RecyclerView
private var cachedSize: Pair<Int, Int>? = null private var cachedSize: Pair<Int, Int>? = null

View File

@ -17,9 +17,9 @@
package im.vector.riotredesign.features.home.room.list package im.vector.riotredesign.features.home.room.list


import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import javax.inject.Inject


class AlphabeticalRoomComparator class AlphabeticalRoomComparator @Inject constructor() : Comparator<RoomSummary> {
: Comparator<RoomSummary> {


override fun compare(leftRoomSummary: RoomSummary?, rightRoomSummary: RoomSummary?): Int { override fun compare(leftRoomSummary: RoomSummary?, rightRoomSummary: RoomSummary?): Int {
return when { return when {

View File

@ -17,8 +17,9 @@
package im.vector.riotredesign.features.home.room.list package im.vector.riotredesign.features.home.room.list


import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import javax.inject.Inject


class ChronologicalRoomComparator : Comparator<RoomSummary> { class ChronologicalRoomComparator @Inject constructor() : Comparator<RoomSummary> {


override fun compare(leftRoomSummary: RoomSummary?, rightRoomSummary: RoomSummary?): Int { override fun compare(leftRoomSummary: RoomSummary?, rightRoomSummary: RoomSummary?): Int {
var rightTimestamp = 0L var rightTimestamp = 0L

View File

@ -23,7 +23,11 @@ import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.airbnb.mvrx.* import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
@ -36,7 +40,7 @@ import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.home.room.list.widget.FabMenuView import im.vector.riotredesign.features.home.room.list.widget.FabMenuView
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_list.* import kotlinx.android.synthetic.main.fragment_room_list.*
import org.koin.android.ext.android.inject import javax.inject.Inject


@Parcelize @Parcelize
data class RoomListParams( data class RoomListParams(
@ -61,7 +65,8 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
} }


private val roomListParams: RoomListParams by args() private val roomListParams: RoomListParams by args()
private val roomController by inject<RoomSummaryController>() @Inject lateinit var roomController: RoomSummaryController
@Inject lateinit var roomListViewModelFactory: RoomListViewModel.Factory
private val roomListViewModel: RoomListViewModel by fragmentViewModel() private val roomListViewModel: RoomListViewModel by fragmentViewModel()


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

View File

@ -19,9 +19,12 @@ package im.vector.riotredesign.features.home.room.list
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import arrow.core.Option import arrow.core.Option
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.BehaviorRelay
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
@ -29,26 +32,27 @@ import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.core.utils.LiveEvent
import im.vector.riotredesign.features.home.HomeRoomListObservableStore import im.vector.riotredesign.features.home.HomeRoomListObservableStore
import org.koin.android.ext.android.get


typealias RoomListFilterName = CharSequence typealias RoomListFilterName = CharSequence


class RoomListViewModel(initialState: RoomListViewState, class RoomListViewModel @AssistedInject constructor(@Assisted initialState: RoomListViewState,
private val session: Session, private val session: Session,
private val homeRoomListObservableSource: HomeRoomListObservableStore, private val homeRoomListObservableSource: HomeRoomListObservableStore,
private val alphabeticalRoomComparator: AlphabeticalRoomComparator, private val alphabeticalRoomComparator: AlphabeticalRoomComparator,
private val chronologicalRoomComparator: ChronologicalRoomComparator) private val chronologicalRoomComparator: ChronologicalRoomComparator)
: VectorViewModel<RoomListViewState>(initialState) { : VectorViewModel<RoomListViewState>(initialState) {


@AssistedInject.Factory
interface Factory {
fun create(initialState: RoomListViewState): RoomListViewModel
}

companion object : MvRxViewModelFactory<RoomListViewModel, RoomListViewState> { companion object : MvRxViewModelFactory<RoomListViewModel, RoomListViewState> {


@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomListViewState): RoomListViewModel? { override fun create(viewModelContext: ViewModelContext, state: RoomListViewState): RoomListViewModel? {
val currentSession = viewModelContext.activity.get<Session>() val fragment: RoomListFragment = (viewModelContext as FragmentViewModelContext).fragment()
val homeRoomListObservableSource = viewModelContext.activity.get<HomeRoomListObservableStore>() return fragment.roomListViewModelFactory.create(state)
val chronologicalRoomComparator = viewModelContext.activity.get<ChronologicalRoomComparator>()
val alphabeticalRoomComparator = viewModelContext.activity.get<AlphabeticalRoomComparator>()
return RoomListViewModel(state, currentSession, homeRoomListObservableSource, alphabeticalRoomComparator, chronologicalRoomComparator)
} }
} }



View File

@ -27,10 +27,11 @@ import im.vector.riotredesign.core.resources.DateProvider
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
import javax.inject.Inject


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


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

View File

@ -19,11 +19,12 @@
package im.vector.riotredesign.features.html package im.vector.riotredesign.features.html


import android.content.Context import android.content.Context
import android.text.style.ClickableSpan
import android.text.style.URLSpan import android.text.style.URLSpan
import androidx.appcompat.app.AppCompatActivity
import im.vector.matrix.android.api.permalinks.PermalinkData import im.vector.matrix.android.api.permalinks.PermalinkData
import im.vector.matrix.android.api.permalinks.PermalinkParser import im.vector.matrix.android.api.permalinks.PermalinkParser
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.glide.GlideRequests import im.vector.riotredesign.core.glide.GlideRequests
import org.commonmark.node.BlockQuote import org.commonmark.node.BlockQuote
import org.commonmark.node.HtmlBlock import org.commonmark.node.HtmlBlock
@ -50,13 +51,12 @@ import ru.noties.markwon.html.tag.SubScriptHandler
import ru.noties.markwon.html.tag.SuperScriptHandler import ru.noties.markwon.html.tag.SuperScriptHandler
import ru.noties.markwon.html.tag.UnderlineHandler import ru.noties.markwon.html.tag.UnderlineHandler
import java.util.Arrays.asList import java.util.Arrays.asList
import javax.inject.Inject


class EventHtmlRenderer(glideRequests: GlideRequests, class EventHtmlRenderer @Inject constructor(context: AppCompatActivity,
context: Context, session: Session) {
session: Session) {

private val markwon = Markwon.builder(context) private val markwon = Markwon.builder(context)
.usePlugin(MatrixPlugin.create(glideRequests, context, session)) .usePlugin(MatrixPlugin.create(GlideApp.with(context), context, session))
.build() .build()


fun render(text: String): CharSequence { fun render(text: String): CharSequence {

View File

@ -20,8 +20,9 @@ import android.app.Activity
import android.app.Application import android.app.Application
import android.os.Bundle import android.os.Bundle
import im.vector.riotredesign.features.popup.PopupAlertManager import im.vector.riotredesign.features.popup.PopupAlertManager
import javax.inject.Inject


class VectorActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks { class VectorActivityLifecycleCallbacks @Inject constructor() : Application.ActivityLifecycleCallbacks {
override fun onActivityPaused(activity: Activity) { override fun onActivityPaused(activity: Activity) {
} }



View File

@ -27,9 +27,9 @@ import im.vector.riotredesign.features.home.room.detail.RoomDetailArgs
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryActivity import im.vector.riotredesign.features.roomdirectory.RoomDirectoryActivity
import im.vector.riotredesign.features.roomdirectory.roompreview.RoomPreviewActivity import im.vector.riotredesign.features.roomdirectory.roompreview.RoomPreviewActivity
import im.vector.riotredesign.features.settings.VectorSettingsActivity import im.vector.riotredesign.features.settings.VectorSettingsActivity
import javax.inject.Inject


class DefaultNavigator : Navigator { class DefaultNavigator @Inject constructor() : Navigator {



override fun openRoom(roomId: String, context: Context) { override fun openRoom(roomId: String, context: Context) {
val args = RoomDetailArgs(roomId) val args = RoomDetailArgs(roomId)

Some files were not shown because too many files have changed in this diff Show More