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
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
.contains(EventEntityFields.CONTENT, displayName)
.equalTo(EventEntityFields.CONTENT, displayName)
.distinct(EventEntityFields.STATE_KEY)
.findAll()
.size == 1

View File

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

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

// DI
implementation "org.koin:koin-android:$koin_version"
implementation "org.koin:koin-android-scope:$koin_version"
implementation "com.google.dagger:dagger:$daggerVersion"
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
gplayImplementation 'com.google.firebase:firebase-core:16.0.8'

View File

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


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

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

View File

@ -31,67 +31,56 @@ import com.github.piasy.biv.BigImageViewer
import com.github.piasy.biv.loader.glide.GlideImageLoader
import com.jakewharton.threetenabp.AndroidThreeTen
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.crypto.keysbackup.KeysBackupModule
import im.vector.riotredesign.features.home.HomeModule
import im.vector.riotredesign.features.lifecycle.VectorActivityLifecycleCallbacks
import im.vector.riotredesign.features.rageshake.VectorFileLogger
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 javax.inject.Inject


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

lateinit var appContext: Context
//font thread handler
private var mFontThreadHandler: Handler? = null

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

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

VectorUncaughtExceptionHandler.activate(this)

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

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

AndroidThreeTen.init(this)
BigImageViewer.initialize(GlideImageLoader.with(applicationContext))
EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
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)
registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks())

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

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

FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
vectorConfiguration.initConfiguration()
}

override fun injector(): VectorComponent {
return vectorComponent
}

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

private fun getFontThreadHandler(): Handler {
if (mFontThreadHandler == null) {
val handlerThread = HandlerThread("fonts")
handlerThread.start()
mFontThreadHandler = Handler(handlerThread.looper)
return fontThreadHandler ?: createFontThreadHandler().also {
fontThreadHandler = it
}
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 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
* distributed under the License is distributed on an "AS IS" BASIS,
@ -14,22 +14,18 @@
* 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 org.koin.dsl.module.module
import androidx.appcompat.app.AppCompatActivity
import dagger.Module
import dagger.Provides
import im.vector.riotredesign.core.glide.GlideApp

class KeysBackupModule {
@Module
object ScreenModule {

companion object {
const val KEYS_BACKUP_SCOPE = "KEYS_BACKUP_SCOPE"
}
@Provides
@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.riotredesign.R
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 {

View File

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

fun AppCompatActivity.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) {
childFragmentManager.inTransaction { replace(frameId, fragment).addToBackStack(tag) }
}
}

View File

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

class ConfigurationViewModel : ViewModel(), KoinComponent {

private val vectorConfiguration: VectorConfiguration by inject()
class ConfigurationViewModel @Inject constructor(
private val vectorConfiguration: VectorConfiguration
) : ViewModel() {

private var currentConfigurationValue: String? = null

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

_activityRestarter.postValue(LiveEvent(Unit))
}
}

View File

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

import android.os.Bundle
import android.view.View
import android.widget.ProgressBar
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.core.extensions.hideKeyboard
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.
@ -43,7 +44,13 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() {
@BindView(R.id.waiting_view_status_horizontal_progress)
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() {
configureToolbar(toolbar)

View File

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


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

private val vectorConfiguration: VectorConfiguration by inject()

protected lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var vectorConfiguration: VectorConfiguration
private lateinit var configurationViewModel: ConfigurationViewModel

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

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

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

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

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

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


override fun injector(): ScreenComponent {
return screenComponent
}

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

View File

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

import android.os.Bundle
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.LayoutRes
import androidx.annotation.MainThread
import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.ViewModelProvider
import butterknife.ButterKnife
import butterknife.Unbinder
import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MvRx
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 io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import org.koin.android.ext.android.inject
import org.koin.core.parameter.parametersOf
import timber.log.Timber

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

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

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

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

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

override fun onDestroy() {
super.onDestroy()

uiDisposables.dispose()
}

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

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

View File

@ -18,14 +18,15 @@

package im.vector.riotredesign.core.resources

import android.content.Context
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
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 {
return ContextCompat.getColor(context, colorRes)

View File

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

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

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

View File

@ -19,8 +19,9 @@ package im.vector.riotredesign.core.resources
import android.content.res.Resources
import androidx.annotation.ArrayRes
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

View File

@ -20,8 +20,9 @@ import android.content.res.Resources
import androidx.annotation.NonNull
import androidx.annotation.PluralsRes
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

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.features.notifications.NotifiableEventResolver
import im.vector.riotredesign.features.notifications.NotificationUtils
import org.koin.android.ext.android.inject
import timber.log.Timber
import java.util.concurrent.TimeUnit

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

when (action) {
ACTION_START,
ACTION_GO_TO_FOREGROUND ->
ACTION_GO_TO_FOREGROUND ->
when (serviceState) {
ServiceState.INIT ->
ServiceState.INIT ->
start(false)
ServiceState.CATCHUP ->
// 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_GO_TO_BACKGROUND,
ACTION_LOGOUT ->
ACTION_LOGOUT ->
stop()
ACTION_PUSH_RECEIVED,
ACTION_SIMULATED_PUSH_RECEIVED ->
when (serviceState) {
ServiceState.INIT ->
ServiceState.INIT ->
start(true)
ServiceState.CATCHUP ->
catchup(true)
@ -203,17 +202,17 @@ class EventStreamServiceX : VectorService() {
// Nothing to do
Unit
}
ACTION_PUSH_UPDATE -> pushStatusUpdate()
ACTION_BOOT_COMPLETE -> {
ACTION_PUSH_UPDATE -> pushStatusUpdate()
ACTION_BOOT_COMPLETE -> {
// No FCM only
mSimulatePushImmediate = true
stop()
}
ACTION_APPLICATION_UPGRADE -> {
ACTION_APPLICATION_UPGRADE -> {
// FDroid only
catchup(true)
}
else -> {
else -> {
// Should not happen
}
}
@ -247,8 +246,8 @@ class EventStreamServiceX : VectorService() {
val pushSimulatorRequest = OneTimeWorkRequestBuilder<PushSimulatorWorker>()
.setInitialDelay(delay.toLong(), TimeUnit.MILLISECONDS)
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.addTag(PUSH_SIMULATOR_REQUEST_TAG)
.build()


View File

@ -20,8 +20,9 @@ import com.airbnb.epoxy.TypedEpoxyController
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.autocomplete.AutocompleteClickListener
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


View File

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

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

init {

View File

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

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

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

var enabled: Boolean = true

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

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 im.vector.matrix.android.api.session.user.model.User
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


View File

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

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

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 timber.log.Timber
import java.util.*
import javax.inject.Inject

/**
* 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?
fun onConfigurationChanged(newConfig: Configuration?) {
if (Locale.getDefault().toString() != VectorLocale.applicationLocale.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,
FontScale.getFontScalePrefValue(context),
ThemeUtils.getApplicationTheme(context))
FontScale.getFontScalePrefValue(context),
ThemeUtils.getApplicationTheme(context))
}
}

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

/**

View File

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

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

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

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

View File

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

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

mKeyTextEdit.setText(viewModel.recoveryCode.value)

View File

@ -18,7 +18,6 @@ package im.vector.riotredesign.features.crypto.keysbackup.restore
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreSharedViewModel
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.listeners.StepProgressListener
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.ui.views.KeysBackupBanner
import timber.log.Timber
import javax.inject.Inject

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

var recoveryCode: 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.platform.VectorBaseFragment
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
import im.vector.riotredesign.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel

class KeysBackupRestoreFromPassphraseFragment : VectorBaseFragment() {

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

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



View File

@ -18,7 +18,6 @@ package im.vector.riotredesign.features.crypto.keysbackup.restore
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreSharedViewModel
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.listeners.StepProgressListener
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.ui.views.KeysBackupBanner
import timber.log.Timber
import javax.inject.Inject

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

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

View File

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

import android.content.Context
import androidx.lifecycle.LiveData
@ -21,13 +21,14 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import im.vector.matrix.android.api.MatrixCallback
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.model.ImportRoomKeysResult
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.WaitingViewData
import im.vector.riotredesign.core.utils.LiveEvent
import javax.inject.Inject

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

companion object {
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 butterknife.BindView
import butterknife.OnClick
import im.vector.fragments.keysbackup.restore.KeysBackupRestoreSharedViewModel
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.core.utils.LiveEvent
@ -40,7 +39,7 @@ class KeysBackupRestoreSuccessFragment : VectorBaseFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
sharedViewModel = activity?.run {
ViewModelProviders.of(this).get(KeysBackupRestoreSharedViewModel::class.java)
ViewModelProviders.of(this, viewModelFactory).get(KeysBackupRestoreSharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")

sharedViewModel.importKeyResult?.let {

View File

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

import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.viewModel
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.WaitingViewData
import im.vector.riotredesign.features.crypto.keysbackup.KeysBackupModule
import org.koin.android.scope.ext.android.bindScope
import org.koin.android.scope.ext.android.getOrCreateScope
import javax.inject.Inject


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

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() {
super.initUiAndData()

bindScope(getOrCreateScope(KeysBackupModule.KEYS_BACKUP_SCOPE))

if (supportFragmentManager.fragments.isEmpty()) {
supportFragmentManager.beginTransaction()
.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.withState
import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.injector
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.setup.KeysBackupSetupActivity
import kotlinx.android.synthetic.main.fragment_keys_backup_settings.*
import org.koin.android.ext.android.inject
import org.koin.android.scope.ext.android.bindScope
import org.koin.android.scope.ext.android.getOrCreateScope
import javax.inject.Inject

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

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

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()

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

bindScope(getOrCreateScope(KeysBackupModule.KEYS_BACKUP_SCOPE))
}

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 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.crypto.keysbackup.KeysBackupState
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.core.epoxy.errorWithRetryItem
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 java.util.*
import javax.inject.Inject

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

var listener: Listener? = null


View File

@ -15,7 +15,15 @@
*/
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.session.Session
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.internal.crypto.keysbackup.model.KeysBackupVersionTrust
import im.vector.riotredesign.core.platform.VectorViewModel
import org.koin.android.ext.android.get


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

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

companion object : MvRxViewModelFactory<KeysBackupSettingsViewModel, KeysBackupSettingViewState> {

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

val firstState = state.copy(
keysBackupState = session.getKeysBackupService().state,
keysBackupVersion = session.getKeysBackupService().keysBackupVersion
)

return KeysBackupSettingsViewModel(firstState, session)
val activity: KeysBackupManageActivity = (viewModelContext as ActivityViewModelContext).activity()
return activity.keysBackupSettingsViewModelFactory.create(state)
}
}

private var keysBackupService: KeysBackupService = session.getKeysBackupService()

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

getKeysBackupTrust()
}

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

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

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

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.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import im.vector.fragments.keysbackup.setup.KeysBackupSetupSharedViewModel
import im.vector.riotredesign.R
import im.vector.riotredesign.core.dialogs.ExportKeysDialog
import im.vector.riotredesign.core.extensions.observeEvent
@ -42,7 +41,7 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() {
.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.initSession(session)


View File

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

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

import android.content.Context
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.utils.LiveEvent
import timber.log.Timber
import javax.inject.Inject

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

companion object {
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 butterknife.BindView
import butterknife.OnClick
import im.vector.fragments.keysbackup.setup.KeysBackupSetupSharedViewModel
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.core.utils.LiveEvent
@ -51,7 +50,7 @@ class KeysBackupSetupStep1Fragment : VectorBaseFragment() {
super.onActivityCreated(savedInstanceState)

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

viewModel.showManualExport.observe(this, Observer {

View File

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

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

viewModel.shouldPromptOnBack = true

View File

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



View File

@ -40,6 +40,8 @@ import timber.log.Timber
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.collections.ArrayList
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
* depending on user action)
*/
class KeyRequestHandler(val context: Context,
val session: Session)

@Singleton
class KeyRequestHandler @Inject constructor(val context: Context,
val session: Session)
: RoomKeysRequestListener,
SasVerificationService.SasVerificationListener {
SasVerificationService.SasVerificationListener {

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

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

session.addRoomKeysRequestListener(this)
}

fun ensureStarted() = Unit

/**
* Handle incoming key request.
*
@ -194,8 +195,8 @@ class KeyRequestHandler(val context: Context,
Runnable {
alert.weakCurrentActivity?.get()?.let {
val intent = SASVerificationActivity.outgoingIntent(it,
session.sessionParams.credentials.userId,
userId, deviceId)
session.sessionParams.credentials.userId,
userId, deviceId)
it.startActivity(intent)
}
},
@ -246,8 +247,8 @@ class KeyRequestHandler(val context: Context,
val alertMgrUniqueKey = alertManagerId(deviceId!!, userId!!)
alertsToRequests[alertMgrUniqueKey]?.removeAll {
it.deviceId == request.deviceId
&& it.userId == request.userId
&& it.requestId == request.requestId
&& it.userId == request.userId
&& it.requestId == request.requestId
}
if (alertsToRequests[alertMgrUniqueKey]?.isEmpty() == true) {
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.riotredesign.R
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.
*/
class IncomingVerificationRequestHandler(val context: Context,
private val session: Session) : SasVerificationService.SasVerificationListener {
@Singleton
class IncomingVerificationRequestHandler @Inject constructor(val context: Context,
private val session: Session
) : SasVerificationService.SasVerificationListener {

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

fun ensureStarted() = Unit

override fun transactionCreated(tx: SasVerificationTransaction) {}

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

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

View File

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

override fun 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)

if (isFirstCreation()) {

View File

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

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

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

View File

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



View File

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

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

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

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

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

View File

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

viewModel = activity?.run {
ViewModelProviders.of(this).get(SasVerificationViewModel::class.java)
ViewModelProviders.of(this, viewModelFactory).get(SasVerificationViewModel::class.java)
} ?: 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.user.model.User
import im.vector.riotredesign.core.utils.LiveEvent
import javax.inject.Inject


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


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.workers.signout.SignOutUiWorker
import kotlinx.android.synthetic.main.activity_home.*
import org.koin.android.ext.android.inject
import org.koin.android.scope.ext.android.bindScope
import org.koin.android.scope.ext.android.getOrCreateScope
import javax.inject.Inject


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

private val homeActivityViewModel: HomeActivityViewModel by viewModel()
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
private val incomingVerificationRequestHandler by inject<IncomingVerificationRequestHandler>()
@Inject lateinit var incomingVerificationRequestHandler: IncomingVerificationRequestHandler
// TODO Move this elsewhere
private val keyRequestHandler by inject<KeyRequestHandler>()
@Inject lateinit var keyRequestHandler: KeyRequestHandler

private var progress: ProgressDialog? = null

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

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

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

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

View File

@ -19,10 +19,12 @@ package im.vector.riotredesign.features.home
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import arrow.core.Option
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
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.session.Session
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 io.reactivex.Observable
import io.reactivex.functions.BiFunction
import org.koin.android.ext.android.get
import java.util.concurrent.TimeUnit

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

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

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

companion object : MvRxViewModelFactory<HomeActivityViewModel, EmptyState> {

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


private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean>
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 kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_home_detail.*
import org.koin.android.ext.android.inject
import javax.inject.Inject


@Parcelize
@ -67,7 +67,8 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate {
private val viewModel: HomeDetailViewModel by fragmentViewModel()
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 {
return R.layout.fragment_home_detail
@ -76,7 +77,7 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
currentDisplayMode = savedInstanceState?.getSerializable(CURRENT_DISPLAY_MODE) as? RoomListFragment.DisplayMode
?: RoomListFragment.DisplayMode.HOME
?: RoomListFragment.DisplayMode.HOME

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

@ -89,7 +90,7 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate {
private fun setupKeysBackupBanner() {
// Keys backup banner
// 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)


View File

@ -16,28 +16,34 @@

package im.vector.riotredesign.features.home

import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
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.rx.rx
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
*/
class HomeDetailViewModel(initialState: HomeDetailViewState,
private val session: Session,
private val homeRoomListStore: HomeRoomListObservableStore)
class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: HomeDetailViewState,
private val session: Session,
private val homeRoomListStore: HomeRoomListObservableStore)
: VectorViewModel<HomeDetailViewState>(initialState) {

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

companion object : MvRxViewModelFactory<HomeDetailViewModel, HomeDetailViewState> {

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

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

val roomsNotifications = summaries
.filter { !it.isDirect }
.map { it.notificationCount }
.takeIf { it.isNotEmpty() }
?.sumBy { i -> i }
?: 0
.filter { !it.isDirect }
.map { it.notificationCount }
.takeIf { it.isNotEmpty() }
?.sumBy { i -> i }
?: 0
val roomsHasHighlight = summaries
.filter { !it.isDirect }
.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.features.home.group.GroupListFragment
import kotlinx.android.synthetic.main.fragment_home_drawer.*
import org.koin.android.ext.android.inject
import javax.inject.Inject

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


View File

@ -16,89 +16,20 @@

package im.vector.riotredesign.features.home

import androidx.fragment.app.Fragment
import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.resources.ColorProvider
import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandController
import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter
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
import android.os.Handler
import dagger.Module
import dagger.Provides
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventControllerHandler
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineAsyncHelper

class HomeModule {
@Module
object HomeModule {

companion object {
const val HOME_SCOPE = "HOME_SCOPE"
const val ROOM_DETAIL_SCOPE = "ROOM_DETAIL_SCOPE"
@Provides
@JvmStatic
@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

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 kotlinx.android.synthetic.main.activity_home.*
import timber.log.Timber
import javax.inject.Inject

class HomeNavigator {
class HomeNavigator @Inject constructor() {

var activity: HomeActivity? = null

private var rootRoomId: String? = null

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.PermalinkParser
import im.vector.riotredesign.features.navigation.Navigator
import javax.inject.Inject

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

fun launch(deepLink: String?) {
val uri = deepLink?.let { Uri.parse(it) }
@ -35,16 +36,16 @@ class HomePermalinkHandler(private val homeNavigator: HomeNavigator,
}
val permalinkData = PermalinkParser.parse(deepLink)
when (permalinkData) {
is PermalinkData.EventLink -> {
is PermalinkData.EventLink -> {
homeNavigator.openRoomDetail(permalinkData.roomIdOrAlias, permalinkData.eventId, navigator)
}
is PermalinkData.RoomLink -> {
is PermalinkData.RoomLink -> {
homeNavigator.openRoomDetail(permalinkData.roomIdOrAlias, null, navigator)
}
is PermalinkData.GroupLink -> {
is PermalinkData.GroupLink -> {
homeNavigator.openGroupDetail(permalinkData.groupId)
}
is PermalinkData.UserLink -> {
is PermalinkData.UserLink -> {
homeNavigator.openUserDetail(permalinkData.userId)
}
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.RoomListFragment
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>> {
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.features.home.HomeNavigator
import kotlinx.android.synthetic.main.fragment_group_list.*
import org.koin.android.ext.android.inject
import javax.inject.Inject

class GroupListFragment : VectorBaseFragment(), GroupSummaryController.Callback {

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

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


View File

@ -19,8 +19,11 @@ package im.vector.riotredesign.features.home.group
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import arrow.core.Option
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
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.group.model.GroupSummary
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.resources.StringProvider
import im.vector.riotredesign.core.utils.LiveEvent
import org.koin.android.ext.android.get

const val ALL_COMMUNITIES_GROUP_ID = "ALL_COMMUNITIES_GROUP_ID"

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


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

companion object : MvRxViewModelFactory<GroupListViewModel, GroupListViewState> {

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


View File

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

import com.airbnb.epoxy.TypedEpoxyController
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


View File

@ -19,5 +19,8 @@ package im.vector.riotredesign.features.home.group
import arrow.core.Option
import im.vector.matrix.android.api.session.group.model.GroupSummary
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.room.model.EditAggregatedSummary
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.user.model.User
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.glide.GlideApp
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.CommandAutocompletePolicy
import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter
import im.vector.riotredesign.features.command.Command
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.getColorFromUserId
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.merge_composer_layout.view.*
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.html.HtmlPlugin
import timber.log.Timber
import java.io.File
import javax.inject.Inject


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

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

private val roomDetailViewModel: RoomDetailViewModel 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

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

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


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

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

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

// Add the span
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)

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

View File

@ -19,10 +19,13 @@ package im.vector.riotredesign.features.home.room.detail
import android.text.TextUtils
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext
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.session.Session
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 org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
import org.koin.android.ext.android.get
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit


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

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

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

companion object : MvRxViewModelFactory<RoomDetailViewModel, RoomDetailViewState> {

const val PAGINATION_COUNT = 50

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

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

val finalText = legacyRiotQuoteText(textMsg, action.text)

View File

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

import arrow.core.Option
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
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.user.model.User
import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.features.home.room.detail.RoomDetailFragment
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import org.koin.android.ext.android.get
import java.util.concurrent.TimeUnit

typealias AutocompleteUserQuery = CharSequence

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


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

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

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

companion object : MvRxViewModelFactory<TextComposerViewModel, TextComposerViewState> {

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

@ -80,7 +89,7 @@ class TextComposerViewModel(initialState: TextComposerViewState,
} else {
users.filter {
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.EpoxyModel
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.TimelineEvent
import im.vector.riotredesign.core.epoxy.LoadingItem_
import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.features.home.room.detail.timeline.factory.TimelineItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.helper.*
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.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.VideoContentRenderer
import org.threeten.bp.LocalDateTime
import javax.inject.Inject

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

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
// We then are sure we always have items up to date.
if (modelCache[position] == null
|| modelCache[position]?.mergedHeaderModel != null
|| modelCache[position]?.formattedDayModel != null) {
|| modelCache[position]?.mergedHeaderModel != null
|| modelCache[position]?.formattedDayModel != null) {
modelCache[position] = buildItemModels(position, currentSnapshot)
}
}
@ -248,7 +261,7 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
// => handle case where paginating from mergeable events and we get more
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey)
?: true
?: true
val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState }
if (isCollapsed) {
collapsedEventIds.addAll(mergedEventIds)

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.ViewModel
import im.vector.riotredesign.core.utils.LiveEvent
import javax.inject.Inject

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

data class ActionData(
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.room.detail.timeline.item.MessageInformationData
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
@ -43,6 +44,7 @@ import kotlinx.android.synthetic.main.bottom_sheet_message_actions.*
*/
class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() {

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

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.MvRxViewModelFactory
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.events.model.EventType
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.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
import org.commonmark.parser.Parser
import org.koin.android.ext.android.get
import org.koin.core.parameter.parametersOf
import ru.noties.markwon.Markwon
import ru.noties.markwon.html.HtmlPlugin
import timber.log.Timber
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotredesign.features.html.EventHtmlRenderer
import java.text.SimpleDateFormat
import java.util.*


data class MessageActionState(
val roomId: String,
val eventId: String,
val informationData: MessageInformationData,
val userId: String = "",
val senderName: String = "",
val messageBody: CharSequence? = null,
@ -45,64 +46,77 @@ data class MessageActionState(
val showPreview: Boolean = false,
val canReact: Boolean = false,
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.
*/
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> {

override fun initialState(viewModelContext: ViewModelContext): MessageActionState? {
val currentSession = viewModelContext.activity.get<Session>()
val fragment = (viewModelContext as? FragmentViewModelContext)?.fragment
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
}
override fun create(viewModelContext: ViewModelContext, state: MessageActionState): MessageActionsViewModel? {
val fragment: MessageActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.messageActionViewModelFactory.create(state)
}
}


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 im.vector.riotredesign.R
import im.vector.riotredesign.features.themes.ThemeUtils
import javax.inject.Inject

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

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

private var addSeparators = false

var interactionListener: InteractionListener? = null

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

View File

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

import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
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.events.model.EventType
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.riotredesign.R
import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
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 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...)
*/
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> {

override fun initialState(viewModelContext: ViewModelContext): MessageMenuState? {
// Args are accessible from the context.
val currentSession = viewModelContext.activity.get<Session>()
val parcel = viewModelContext.args as TimelineEventFragmentArgs
val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
?: return null
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"

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

if (!event.sendState.isSent()) {
//Resend and Delete
return MessageMenuState(
//TODO
listOf(
init {
setState { reduceState(this) }
}

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),
// //TODO delete icon
// SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId)
)
)
}

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

if (event.sendState == SendState.SENDING) {
//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))
}

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))
}

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))
}

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)) {
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 (messageContent is MessageImageContent) {
this.add(
SimpleAction(ACTION_SHARE,
R.string.share, R.drawable.ic_share,
currentSession.contentUrlResolver().resolveFullSize(messageContent.url))
R.string.share, R.drawable.ic_share,
session.contentUrlResolver().resolveFullSize(messageContent.url))
)
}
//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)))
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
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
if (event.root.getClearType() != EventType.MESSAGE) return false
return when (messageContent?.type) {
MessageType.MSGTYPE_TEXT,
MessageType.MSGTYPE_NOTICE,
MessageType.MSGTYPE_EMOTE,
MessageType.MSGTYPE_IMAGE,
MessageType.MSGTYPE_VIDEO,
MessageType.MSGTYPE_AUDIO,
MessageType.MSGTYPE_FILE -> true
else -> false

private fun canReply(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.MSGTYPE_IMAGE,
MessageType.MSGTYPE_VIDEO,
MessageType.MSGTYPE_AUDIO,
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 {
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
return event.root.getClearType() == EventType.MESSAGE
}
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 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 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 {
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 {
return when (type) {
MessageType.MSGTYPE_TEXT,
MessageType.MSGTYPE_NOTICE,
MessageType.MSGTYPE_EMOTE,
MessageType.FORMAT_MATRIX_HTML,
MessageType.MSGTYPE_LOCATION -> {
true
}
else -> false
private fun canShare(type: String?): Boolean {
return when (type) {
MessageType.MSGTYPE_IMAGE,
MessageType.MSGTYPE_AUDIO,
MessageType.MSGTYPE_VIDEO -> {
true
}
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 im.vector.riotredesign.EmojiCompatFontProvider
import im.vector.riotredesign.R
import org.koin.android.ext.android.inject
import javax.inject.Inject

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

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? {
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?) {
super.onViewCreated(view, savedInstanceState)

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

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

TransitionManager.beginDelayedTransition(rootLayout)
when (it.agreeTrigleState) {
when (it.agreeTriggleState) {
TriggleState.NONE -> {
quickReact1Text.alpha = 1f
quickReact2Text.alpha = 1f
@ -130,7 +131,7 @@ class QuickReactionFragment : BaseMvRxFragment() {
if (it.selectionResult != null) {
val clikedOn = it.selectionResult.first
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

import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
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.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
@ -32,33 +35,99 @@ enum class TriggleState {
}

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,
/** Pair of 'clickedOn' and current toggles state*/
val selectionResult: Pair<String, List<String>>? = null,
val eventId: String = "") : MvRxState
val selectionResult: Pair<String, List<String>>? = null
) : MvRxState {

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

}


/**
* 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 {
if (isFirst) {
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(
agreeTrigleState = newTriggle,
selectionResult = Pair(agreePositive, getReactions(this, newTriggle, null))
agreeTriggleState = newTriggle,
selectionResult = Pair(AGREE_POSITIVE, getReactions(this, newTriggle, null))
)
}
} else {
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(
agreeTrigleState = agreeTrigleState,
selectionResult = Pair(agreeNegative, getReactions(this, newTriggle, null))
agreeTriggleState = agreeTriggleState,
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
copy(
likeTriggleState = newTriggle,
selectionResult = Pair(likePositive, getReactions(this, null, newTriggle))
selectionResult = Pair(LIKE_POSITIVE, getReactions(this, null, newTriggle))
)
}
} else {
@ -78,7 +147,7 @@ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel
val newTriggle = if (it.likeTriggleState == TriggleState.SECOND) TriggleState.NONE else TriggleState.SECOND
copy(
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> {
return ArrayList<String>(4).apply {
when (newState2 ?: state.likeTriggleState) {
TriggleState.FIRST -> add(likePositive)
TriggleState.SECOND -> add(likeNegative)
TriggleState.FIRST -> add(LIKE_POSITIVE)
TriggleState.SECOND -> add(LIKE_NEGATIVE)
else -> {
}
}
when (newState1 ?: state.agreeTrigleState) {
TriggleState.FIRST -> add(agreePositive)
TriggleState.SECOND -> add(agreeNegative)
when (newState1 ?: state.agreeTriggleState) {
TriggleState.FIRST -> add(AGREE_POSITIVE)
TriggleState.SECOND -> add(AGREE_NEGATIVE)
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.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? {
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.item.MessageInformationData
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_
import javax.inject.Inject

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

fun create(timelineEvent: TimelineEvent): VectorEpoxyModel<*>? {
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.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? {
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
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.toModel
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.timeline.TimelineEvent
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.helper.TimelineDateFormatter
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.media.ImageContentRenderer
import im.vector.riotredesign.features.media.VideoContentRenderer
import me.gujun.android.span.span
import javax.inject.Inject

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

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

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

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

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

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

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

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

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

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

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

@ -395,7 +416,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
}))
.longClickListener { 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.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,
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.MessageTextItem_
import timber.log.Timber
import javax.inject.Inject

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

fun create(event: 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
if (TimelineDisplayableEvents.DEBUG_HIDDEN_EVENT) {
val informationData = MessageInformationData(eventId = event.root.eventId
?: "?",
senderId = event.root.sender ?: "",
sendState = event.sendState,
time = "",
avatarUrl = null,
memberName = "",
showInformation = false
?: "?",
senderId = event.root.sender
?: "",
sendState = event.sendState,
time = "",
avatarUrl = null,
memberName = "",
showInformation = false
)
val messageContent = event.root.content.toModel<MessageContent>()
?: MessageDefaultContent("", "", null, null)
?: MessageDefaultContent("", "", null, null)
MessageTextItem_()
.informationData(informationData)
.message("{ \"type\": ${event.root.type} }")
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
} else {
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.EventType
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.timeline.TimelineEvent
import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName
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? {
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? {
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility
?: return null
?: return null

val formattedVisibility = when (historyVisibility) {
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)
else ->
stringProvider.getString(R.string.notice_display_name_changed_from,
event.sender, prevEventContent?.displayName, eventContent?.displayName)
event.sender, prevEventContent?.displayName, eventContent?.displayName)
}
displayText.append(displayNameText)
}
@ -143,7 +149,7 @@ class NoticeEventFormatter(private val stringProvider: StringProvider) {
when {
eventContent.thirdPartyInvite != null ->
stringProvider.getString(R.string.notice_room_third_party_registered_invite,
targetDisplayName, eventContent.thirdPartyInvite?.displayName)
targetDisplayName, eventContent.thirdPartyInvite?.displayName)
TextUtils.equals(event.stateKey, selfUserId) ->
stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName)
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 org.threeten.bp.LocalDateTime
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 {
DateTimeFormatter.ofPattern("H:mm", localeProvider.current())

View File

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

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

class TimelineMediaSizeProvider {
class TimelineMediaSizeProvider @Inject constructor() {

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

View File

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

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

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

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

View File

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

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 {
var rightTimestamp = 0L

View File

@ -23,7 +23,11 @@ import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
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.session.room.model.Membership
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 kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_list.*
import org.koin.android.ext.android.inject
import javax.inject.Inject

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

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()

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.MutableLiveData
import arrow.core.Option
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
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.room.model.Membership
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.utils.LiveEvent
import im.vector.riotredesign.features.home.HomeRoomListObservableStore
import org.koin.android.ext.android.get

typealias RoomListFilterName = CharSequence

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

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

companion object : MvRxViewModelFactory<RoomListViewModel, RoomListViewState> {

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


View File

@ -27,10 +27,11 @@ import im.vector.riotredesign.core.resources.DateProvider
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.helper.TimelineDateFormatter
import javax.inject.Inject

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

var callback: Callback? = null

View File

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

import android.content.Context
import android.text.style.ClickableSpan
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.PermalinkParser
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.glide.GlideRequests
import org.commonmark.node.BlockQuote
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.UnderlineHandler
import java.util.Arrays.asList
import javax.inject.Inject

class EventHtmlRenderer(glideRequests: GlideRequests,
context: Context,
session: Session) {

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

fun render(text: String): CharSequence {

View File

@ -20,8 +20,9 @@ import android.app.Activity
import android.app.Application
import android.os.Bundle
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) {
}


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.roompreview.RoomPreviewActivity
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) {
val args = RoomDetailArgs(roomId)

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