Merge pull request #4363 from vector-im/feature/fga/rx_flow_migration

Feature/fga/rx flow migration
This commit is contained in:
Benoit Marty 2021-11-04 18:44:48 +01:00 committed by GitHub
commit f3655d4664
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
117 changed files with 1101 additions and 1003 deletions

1
changelog.d/4219.misc Normal file
View file

@ -0,0 +1 @@
Finish migration from RxJava to Flow

View file

@ -17,7 +17,7 @@ def arrow = "0.8.2"
def markwon = "4.6.2" def markwon = "4.6.2"
def moshi = "1.12.0" def moshi = "1.12.0"
def lifecycle = "2.2.0" def lifecycle = "2.2.0"
def rxBinding = "3.1.0" def flowBinding = "1.2.0"
def epoxy = "4.6.2" def epoxy = "4.6.2"
def mavericks = "2.4.0" def mavericks = "2.4.0"
def glide = "4.12.0" def glide = "4.12.0"
@ -41,7 +41,8 @@ ext.libs = [
jetbrains : [ jetbrains : [
'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines", 'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines",
'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines", 'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines",
'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines" 'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines",
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
], ],
androidx : [ androidx : [
'appCompat' : "androidx.appcompat:appcompat:1.3.1", 'appCompat' : "androidx.appcompat:appcompat:1.3.1",
@ -102,7 +103,6 @@ ext.libs = [
'epoxyProcessor' : "com.airbnb.android:epoxy-processor:$epoxy", 'epoxyProcessor' : "com.airbnb.android:epoxy-processor:$epoxy",
'epoxyPaging' : "com.airbnb.android:epoxy-paging:$epoxy", 'epoxyPaging' : "com.airbnb.android:epoxy-paging:$epoxy",
'mavericks' : "com.airbnb.android:mavericks:$mavericks", 'mavericks' : "com.airbnb.android:mavericks:$mavericks",
'mavericksRx' : "com.airbnb.android:mavericks-rxjava2:$mavericks",
'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks" 'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks"
], ],
mockk : [ mockk : [
@ -115,13 +115,13 @@ ext.libs = [
'bigImageViewer' : "com.github.piasy:BigImageViewer:$bigImageViewer", 'bigImageViewer' : "com.github.piasy:BigImageViewer:$bigImageViewer",
'glideImageLoader' : "com.github.piasy:GlideImageLoader:$bigImageViewer", 'glideImageLoader' : "com.github.piasy:GlideImageLoader:$bigImageViewer",
'progressPieIndicator' : "com.github.piasy:ProgressPieIndicator:$bigImageViewer", 'progressPieIndicator' : "com.github.piasy:ProgressPieIndicator:$bigImageViewer",
'glideImageViewFactory' : "com.github.piasy:GlideImageViewFactory:$bigImageViewer" 'glideImageViewFactory' : "com.github.piasy:GlideImageViewFactory:$bigImageViewer",
'flowBinding' : "io.github.reactivecircus.flowbinding:flowbinding-android:$flowBinding",
'flowBindingAppcompat' : "io.github.reactivecircus.flowbinding:flowbinding-appcompat:$flowBinding",
'flowBindingMaterial' : "io.github.reactivecircus.flowbinding:flowbinding-material:$flowBinding"
], ],
jakewharton : [ jakewharton : [
'timber' : "com.jakewharton.timber:timber:5.0.1", 'timber' : "com.jakewharton.timber:timber:5.0.1"
'rxbinding' : "com.jakewharton.rxbinding3:rxbinding:$rxBinding",
'rxbindingAppcompat' : "com.jakewharton.rxbinding3:rxbinding-appcompat:$rxBinding",
'rxbindingMaterial' : "com.jakewharton.rxbinding3:rxbinding-material:$rxBinding"
], ],
jsonwebtoken: [ jsonwebtoken: [
'jjwtApi' : "io.jsonwebtoken:jjwt-api:$jjwt", 'jjwtApi' : "io.jsonwebtoken:jjwt-api:$jjwt",

41
docs/rx_flow_migration.md Normal file
View file

@ -0,0 +1,41 @@
Useful links:
- https://github.com/ReactiveCircus/FlowBinding
- https://ivanisidrowu.github.io/kotlin/2020/08/09/Kotlin-Flow-Migration-And-Testing.html
Rx is now completely removed from Element dependencies.
Some examples of the changes:
```
sharedActionViewModel
.observe()
.subscribe { handleQuickActions(it) }
.disposeOnDestroyView()
```
became
```
sharedActionViewModel
.stream()
.onEach { handleQuickActions(it) }
.launchIn(viewLifecycleOwner.lifecycleScope)
```
Inside fragment use
```
launchIn(viewLifecycleOwner.lifecycleScope)
```
Inside activity use
```
launchIn(lifecycleScope)
```
Inside viewModel use
```
launchIn(viewModelScope)
```
Also be aware that when using these scopes the coroutine is launched on Dispatchers.Main by default.

View file

@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams
import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.pushers.Pusher import org.matrix.android.sdk.api.session.pushers.Pusher
import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
@ -44,10 +45,10 @@ import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
class FlowSession(private val session: Session) { class FlowSession(private val session: Session) {
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Flow<List<RoomSummary>> { fun liveRoomSummaries(queryParams: RoomSummaryQueryParams, sortOrder: RoomSortOrder = RoomSortOrder.NONE): Flow<List<RoomSummary>> {
return session.getRoomSummariesLive(queryParams).asFlow() return session.getRoomSummariesLive(queryParams, sortOrder).asFlow()
.startWith(session.coroutineDispatchers.io) { .startWith(session.coroutineDispatchers.io) {
session.getRoomSummaries(queryParams) session.getRoomSummaries(queryParams, sortOrder)
} }
} }

View file

@ -105,7 +105,6 @@ def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0
android { android {
// Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use // Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use
// Ref: https://issuetracker.google.com/issues/144111441 // Ref: https://issuetracker.google.com/issues/144111441
ndkVersion "21.3.6528147" ndkVersion "21.3.6528147"
@ -333,7 +332,6 @@ configurations {
dependencies { dependencies {
implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android")
implementation project(":matrix-sdk-android-rx")
implementation project(":matrix-sdk-android-flow") implementation project(":matrix-sdk-android-flow")
implementation project(":diff-match-patch") implementation project(":diff-match-patch")
implementation project(":multipicker") implementation project(":multipicker")
@ -374,23 +372,16 @@ dependencies {
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.36' implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.36'
// rx // FlowBinding
implementation libs.rx.rxKotlin implementation libs.github.flowBinding
implementation libs.rx.rxAndroid implementation libs.github.flowBindingAppcompat
implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.1' implementation libs.github.flowBindingMaterial
// RXBinding
implementation libs.jakewharton.rxbinding
implementation libs.jakewharton.rxbindingAppcompat
implementation libs.jakewharton.rxbindingMaterial
implementation libs.airbnb.epoxy implementation libs.airbnb.epoxy
implementation libs.airbnb.epoxyGlide implementation libs.airbnb.epoxyGlide
kapt libs.airbnb.epoxyProcessor kapt libs.airbnb.epoxyProcessor
implementation libs.airbnb.epoxyPaging implementation libs.airbnb.epoxyPaging
implementation libs.airbnb.mavericks implementation libs.airbnb.mavericks
//TODO: remove when entirely migrated to Flow
implementation libs.airbnb.mavericksRx
// Work // Work
implementation libs.androidx.work implementation libs.androidx.work
@ -471,7 +462,7 @@ dependencies {
gplayImplementation 'com.google.android.gms:play-services-oss-licenses:17.0.0' gplayImplementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
implementation "androidx.emoji:emoji-appcompat:1.1.0" implementation "androidx.emoji:emoji-appcompat:1.1.0"
implementation ('com.github.BillCarsonFr:JsonViewer:0.7') implementation('com.github.BillCarsonFr:JsonViewer:0.7')
// WebRTC // WebRTC
// org.webrtc:google-webrtc is for development purposes only // org.webrtc:google-webrtc is for development purposes only
@ -512,6 +503,9 @@ dependencies {
// Plant Timber tree for test // Plant Timber tree for test
testImplementation libs.tests.timberJunitRule testImplementation libs.tests.timberJunitRule
testImplementation libs.airbnb.mavericksTesting testImplementation libs.airbnb.mavericksTesting
testImplementation(libs.jetbrains.coroutinesTest) {
exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
}
// Activate when you want to check for leaks, from time to time. // Activate when you want to check for leaks, from time to time.
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3' //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
@ -525,6 +519,9 @@ dependencies {
androidTestImplementation libs.androidx.espressoIntents androidTestImplementation libs.androidx.espressoIntents
androidTestImplementation libs.tests.kluent androidTestImplementation libs.tests.kluent
androidTestImplementation libs.androidx.coreTesting androidTestImplementation libs.androidx.coreTesting
androidTestImplementation(libs.jetbrains.coroutinesTest) {
exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
}
// Plant Timber tree for test // Plant Timber tree for test
androidTestImplementation libs.tests.timberJunitRule androidTestImplementation libs.tests.timberJunitRule
// "The one who serves a great Espresso" // "The one who serves a great Espresso"

View file

@ -279,25 +279,9 @@ SOFTWARE.
Copyright 2012 The Dagger Authors Copyright 2012 The Dagger Authors
</li> </li>
<li> <li>
<b>rxkotlin</b> <b>FlowBinding</b>
<br/> <br/>
Copyright io.reactivex. Copyright 2019 Yang Chen
</li>
<li>
<b>rxandroid</b>
<br/>
Copyright io.reactivex.
</li>
<li>
<b>rxrelay</b>
<br/>
Copyright 2014 Netflix, Inc.
Copyright 2015 Jake Wharton
</li>
<li>
<b>rxbinding</b>
<br/>
Copyright (C) 2015 Jake Wharton
</li> </li>
<li> <li>
<b>Epoxy</b> <b>Epoxy</b>

View file

@ -24,8 +24,13 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.utils.BehaviorDataSource import im.vector.app.core.utils.BehaviorDataSource
import im.vector.app.features.session.coroutineScope import im.vector.app.features.session.coroutineScope
import im.vector.app.features.ui.UiStateRepository import im.vector.app.features.ui.UiStateRepository
import io.reactivex.disposables.CompositeDisposable import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
@ -54,10 +59,10 @@ class AppStateHandler @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder private val activeSessionHolder: ActiveSessionHolder
) : LifecycleObserver { ) : LifecycleObserver {
private val compositeDisposable = CompositeDisposable() private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val selectedSpaceDataSource = BehaviorDataSource<Option<RoomGroupingMethod>>(Option.empty()) private val selectedSpaceDataSource = BehaviorDataSource<Option<RoomGroupingMethod>>(Option.empty())
val selectedRoomGroupingObservable = selectedSpaceDataSource.observe() val selectedRoomGroupingObservable = selectedSpaceDataSource.stream()
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? { fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? {
// XXX we should somehow make it live :/ just a work around // XXX we should somehow make it live :/ just a work around
@ -105,9 +110,9 @@ class AppStateHandler @Inject constructor(
} }
private fun observeActiveSession() { private fun observeActiveSession() {
sessionDataSource.observe() sessionDataSource.stream()
.distinctUntilChanged() .distinctUntilChanged()
.subscribe { .onEach {
// sessionDataSource could already return a session while activeSession holder still returns null // sessionDataSource could already return a session while activeSession holder still returns null
it.orNull()?.let { session -> it.orNull()?.let { session ->
if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) { if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) {
@ -116,9 +121,8 @@ class AppStateHandler @Inject constructor(
setCurrentGroup(uiStateRepository.getSelectedGroup(session.sessionId), session) setCurrentGroup(uiStateRepository.getSelectedGroup(session.sessionId), session)
} }
} }
}.also {
compositeDisposable.add(it)
} }
.launchIn(coroutineScope)
} }
fun safeActiveSpaceId(): String? { fun safeActiveSpaceId(): String? {
@ -136,7 +140,7 @@ class AppStateHandler @Inject constructor(
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun entersBackground() { fun entersBackground() {
compositeDisposable.clear() coroutineScope.coroutineContext.cancelChildren()
val session = activeSessionHolder.getSafeActiveSession() ?: return val session = activeSessionHolder.getSafeActiveSession() ?: return
when (val currentMethod = selectedSpaceDataSource.currentValue?.orNull() ?: RoomGroupingMethod.BySpace(null)) { when (val currentMethod = selectedSpaceDataSource.currentValue?.orNull() ?: RoomGroupingMethod.BySpace(null)) {
is RoomGroupingMethod.BySpace -> { is RoomGroupingMethod.BySpace -> {

View file

@ -43,7 +43,6 @@ import dagger.hilt.android.HiltAndroidApp
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.configureAndStart import im.vector.app.core.extensions.configureAndStart
import im.vector.app.core.extensions.startSyncing import im.vector.app.core.extensions.startSyncing
import im.vector.app.core.rx.RxConfig
import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.configuration.VectorConfiguration import im.vector.app.features.configuration.VectorConfiguration
import im.vector.app.features.disclaimer.doNotShowDisclaimerDialog import im.vector.app.features.disclaimer.doNotShowDisclaimerDialog
@ -93,7 +92,6 @@ class VectorApplication :
@Inject lateinit var versionProvider: VersionProvider @Inject lateinit var versionProvider: VersionProvider
@Inject lateinit var notificationUtils: NotificationUtils @Inject lateinit var notificationUtils: NotificationUtils
@Inject lateinit var appStateHandler: AppStateHandler @Inject lateinit var appStateHandler: AppStateHandler
@Inject lateinit var rxConfig: RxConfig
@Inject lateinit var popupAlertManager: PopupAlertManager @Inject lateinit var popupAlertManager: PopupAlertManager
@Inject lateinit var pinLocker: PinLocker @Inject lateinit var pinLocker: PinLocker
@Inject lateinit var callManager: WebRtcCallManager @Inject lateinit var callManager: WebRtcCallManager
@ -118,7 +116,6 @@ class VectorApplication :
appContext = this appContext = this
invitesAcceptor.initialize() invitesAcceptor.initialize()
vectorUncaughtExceptionHandler.activate(this) vectorUncaughtExceptionHandler.activate(this)
rxConfig.setupRxPlugin()
// Remove Log handler statically added by Jitsi // Remove Log handler statically added by Jitsi
Timber.forest() Timber.forest()

View file

@ -111,8 +111,8 @@ import im.vector.app.features.roomprofile.members.RoomMemberListFragment
import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment
import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleChooseRestrictedFragment
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleFragment import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleFragment
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedFragment
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment
import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment

View file

@ -137,6 +137,6 @@ object VectorStaticModule {
@Provides @Provides
@JvmStatic @JvmStatic
fun providesCoroutineDispatchers(): CoroutineDispatchers { fun providesCoroutineDispatchers(): CoroutineDispatchers {
return CoroutineDispatchers(io = Dispatchers.IO) return CoroutineDispatchers(io = Dispatchers.IO, computation = Dispatchers.Default)
} }
} }

View file

@ -19,4 +19,6 @@ package im.vector.app.core.dispatchers
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import javax.inject.Inject import javax.inject.Inject
data class CoroutineDispatchers @Inject constructor(val io: CoroutineDispatcher) data class CoroutineDispatchers @Inject constructor(
val io: CoroutineDispatcher,
val computation: CoroutineDispatcher)

View file

@ -0,0 +1,102 @@
/*
* Copyright (c) 2021 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.app.core.flow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ClosedReceiveChannelException
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.produce
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select
@ExperimentalCoroutinesApi
fun <T> Flow<T>.chunk(durationInMillis: Long): Flow<List<T>> {
require(durationInMillis > 0) { "Duration should be greater than 0" }
return flow {
coroutineScope {
val events = ArrayList<T>()
val ticker = fixedPeriodTicker(durationInMillis)
try {
val upstreamValues = produce(capacity = Channel.CONFLATED) {
collect { value -> send(value) }
}
while (isActive) {
var hasTimedOut = false
select<Unit> {
upstreamValues.onReceive {
events.add(it)
}
ticker.onReceive {
hasTimedOut = true
}
}
if (hasTimedOut && events.isNotEmpty()) {
emit(events.toList())
events.clear()
}
}
} catch (e: ClosedReceiveChannelException) {
// drain remaining events
if (events.isNotEmpty()) emit(events.toList())
} finally {
ticker.cancel()
}
}
}
}
@ExperimentalCoroutinesApi
fun <T> Flow<T>.throttleFirst(windowDuration: Long): Flow<T> = flow {
var windowStartTime = System.currentTimeMillis()
var emitted = false
collect { value ->
val currentTime = System.currentTimeMillis()
val delta = currentTime - windowStartTime
if (delta >= windowDuration) {
windowStartTime += delta / windowDuration * windowDuration
emitted = false
}
if (!emitted) {
emit(value)
emitted = true
}
}
}
fun tickerFlow(scope: CoroutineScope, delayMillis: Long, initialDelayMillis: Long = delayMillis): Flow<Unit> {
return scope.fixedPeriodTicker(delayMillis, initialDelayMillis).consumeAsFlow()
}
private fun CoroutineScope.fixedPeriodTicker(delayMillis: Long, initialDelayMillis: Long = delayMillis): ReceiveChannel<Unit> {
require(delayMillis >= 0) { "Expected non-negative delay, but has $delayMillis ms" }
require(initialDelayMillis >= 0) { "Expected non-negative initial delay, but has $initialDelayMillis ms" }
return produce(capacity = 0) {
delay(initialDelayMillis)
while (true) {
channel.send(Unit)
delay(delayMillis)
}
}
}

View file

@ -39,12 +39,12 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentFactory import androidx.fragment.app.FragmentFactory
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.airbnb.mvrx.MavericksView import com.airbnb.mvrx.MavericksView
import com.bumptech.glide.util.Util import com.bumptech.glide.util.Util
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.jakewharton.rxbinding3.view.clicks
import dagger.hilt.android.EntryPointAccessors import dagger.hilt.android.EntryPointAccessors
import im.vector.app.BuildConfig import im.vector.app.BuildConfig
import im.vector.app.R import im.vector.app.R
@ -59,6 +59,7 @@ import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.extensions.restart import im.vector.app.core.extensions.restart
import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs import im.vector.app.features.MainActivityArgs
@ -77,13 +78,12 @@ import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ActivityOtherThemes
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import im.vector.app.receivers.DebugReceiver import im.vector.app.receivers.DebugReceiver
import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.flow.launchIn
import io.reactivex.disposables.CompositeDisposable import kotlinx.coroutines.flow.onEach
import io.reactivex.disposables.Disposable
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.failure.GlobalError
import reactivecircus.flowbinding.android.view.clicks
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), MavericksView { abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), MavericksView {
@ -104,13 +104,12 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
viewEvents viewEvents
.observe() .stream()
.observeOn(AndroidSchedulers.mainThread()) .onEach {
.subscribe {
hideWaitingView() hideWaitingView()
observer(it) observer(it)
} }
.disposeOnDestroy() .launchIn(lifecycleScope)
} }
/* ========================================================================================== /* ==========================================================================================
@ -119,10 +118,9 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
protected fun View.debouncedClicks(onClicked: () -> Unit) { protected fun View.debouncedClicks(onClicked: () -> Unit) {
clicks() clicks()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300)
.observeOn(AndroidSchedulers.mainThread()) .onEach { onClicked() }
.subscribe { onClicked() } .launchIn(lifecycleScope)
.disposeOnDestroy()
} }
/* ========================================================================================== /* ==========================================================================================
@ -133,6 +131,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
private lateinit var sessionListener: SessionListener private lateinit var sessionListener: SessionListener
protected lateinit var bugReporter: BugReporter protected lateinit var bugReporter: BugReporter
private lateinit var pinLocker: PinLocker private lateinit var pinLocker: PinLocker
@Inject @Inject
lateinit var rageShake: RageShake lateinit var rageShake: RageShake
lateinit var navigator: Navigator lateinit var navigator: Navigator
@ -150,7 +149,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
// For debug only // For debug only
private var debugReceiver: DebugReceiver? = null private var debugReceiver: DebugReceiver? = null
private val uiDisposables = CompositeDisposable()
private val restorables = ArrayList<Restorable>() private val restorables = ArrayList<Restorable>()
override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {
@ -175,10 +173,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
return this return this
} }
protected fun Disposable.disposeOnDestroy() {
uiDisposables.add(this)
}
@CallSuper @CallSuper
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Timber.i("onCreate Activity ${javaClass.simpleName}") Timber.i("onCreate Activity ${javaClass.simpleName}")
@ -302,8 +296,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
Timber.i("onDestroy Activity ${javaClass.simpleName}") Timber.i("onDestroy Activity ${javaClass.simpleName}")
uiDisposables.dispose()
} }
private val pinStartForActivityResult = registerStartForActivityResult { activityResult -> private val pinStartForActivityResult = registerStartForActivityResult { activityResult ->

View file

@ -26,21 +26,21 @@ import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.MavericksView import com.airbnb.mvrx.MavericksView
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.jakewharton.rxbinding3.view.clicks
import dagger.hilt.android.EntryPointAccessors import dagger.hilt.android.EntryPointAccessors
import im.vector.app.core.di.ActivityEntryPoint import im.vector.app.core.di.ActivityEntryPoint
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.DimensionConverter
import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.flow.launchIn
import io.reactivex.disposables.CompositeDisposable import kotlinx.coroutines.flow.onEach
import io.reactivex.disposables.Disposable import reactivecircus.flowbinding.android.view.clicks
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
/** /**
* Add Mavericks capabilities, handle DI and bindings. * Add Mavericks capabilities, handle DI and bindings.
@ -108,14 +108,12 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
@CallSuper @CallSuper
override fun onDestroyView() { override fun onDestroyView() {
uiDisposables.clear()
_binding = null _binding = null
super.onDestroyView() super.onDestroyView()
} }
@CallSuper @CallSuper
override fun onDestroy() { override fun onDestroy() {
uiDisposables.dispose()
super.onDestroy() super.onDestroy()
} }
@ -164,27 +162,15 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
arguments = args?.let { Bundle().apply { putParcelable(Mavericks.KEY_ARG, it) } } arguments = args?.let { Bundle().apply { putParcelable(Mavericks.KEY_ARG, it) } }
} }
/* ==========================================================================================
* Disposable
* ========================================================================================== */
private val uiDisposables = CompositeDisposable()
protected fun Disposable.disposeOnDestroyView(): Disposable {
uiDisposables.add(this)
return this
}
/* ========================================================================================== /* ==========================================================================================
* Views * Views
* ========================================================================================== */ * ========================================================================================== */
protected fun View.debouncedClicks(onClicked: () -> Unit) { protected fun View.debouncedClicks(onClicked: () -> Unit) {
clicks() clicks()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300)
.observeOn(AndroidSchedulers.mainThread()) .onEach { onClicked() }
.subscribe { onClicked() } .launchIn(viewLifecycleOwner.lifecycleScope)
.disposeOnDestroyView()
} }
/* ========================================================================================== /* ==========================================================================================
@ -193,11 +179,10 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
viewEvents viewEvents
.observe() .stream()
.observeOn(AndroidSchedulers.mainThread()) .onEach {
.subscribe {
observer(it) observer(it)
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
} }

View file

@ -29,12 +29,12 @@ import androidx.annotation.MainThread
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.airbnb.mvrx.MavericksView import com.airbnb.mvrx.MavericksView
import com.bumptech.glide.util.Util.assertMainThread import com.bumptech.glide.util.Util.assertMainThread
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jakewharton.rxbinding3.view.clicks
import dagger.hilt.android.EntryPointAccessors import dagger.hilt.android.EntryPointAccessors
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActivityEntryPoint import im.vector.app.core.di.ActivityEntryPoint
@ -42,13 +42,13 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.flow.throttleFirst
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.flow.launchIn
import io.reactivex.disposables.CompositeDisposable import kotlinx.coroutines.flow.onEach
import io.reactivex.disposables.Disposable import reactivecircus.flowbinding.android.view.clicks
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView { abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView {
@ -148,7 +148,6 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
@CallSuper @CallSuper
override fun onDestroyView() { override fun onDestroyView() {
Timber.i("onDestroyView Fragment ${javaClass.simpleName}") Timber.i("onDestroyView Fragment ${javaClass.simpleName}")
uiDisposables.clear()
_binding = null _binding = null
super.onDestroyView() super.onDestroyView()
} }
@ -156,7 +155,6 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
@CallSuper @CallSuper
override fun onDestroy() { override fun onDestroy() {
Timber.i("onDestroy Fragment ${javaClass.simpleName}") Timber.i("onDestroy Fragment ${javaClass.simpleName}")
uiDisposables.dispose()
super.onDestroy() super.onDestroy()
} }
@ -221,29 +219,18 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
} }
} }
/* ==========================================================================================
* Disposable
* ========================================================================================== */
private val uiDisposables = CompositeDisposable()
protected fun Disposable.disposeOnDestroyView() {
uiDisposables.add(this)
}
/* ========================================================================================== /* ==========================================================================================
* ViewEvents * ViewEvents
* ========================================================================================== */ * ========================================================================================== */
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
viewEvents viewEvents
.observe() .stream()
.observeOn(AndroidSchedulers.mainThread()) .onEach {
.subscribe {
dismissLoadingDialog() dismissLoadingDialog()
observer(it) observer(it)
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
/* ========================================================================================== /* ==========================================================================================
@ -252,10 +239,9 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
protected fun View.debouncedClicks(onClicked: () -> Unit) { protected fun View.debouncedClicks(onClicked: () -> Unit) {
clicks() clicks()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300)
.observeOn(AndroidSchedulers.mainThread()) .onEach { onClicked() }
.subscribe { onClicked() } .launchIn(viewLifecycleOwner.lifecycleScope)
.disposeOnDestroyView()
} }
/* ========================================================================================== /* ==========================================================================================

View file

@ -16,53 +16,17 @@
package im.vector.app.core.platform package im.vector.app.core.platform
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.BaseMvRxViewModel
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Success import com.airbnb.mvrx.MavericksViewModel
import im.vector.app.core.utils.DataSource import im.vector.app.core.utils.DataSource
import im.vector.app.core.utils.PublishDataSource import im.vector.app.core.utils.PublishDataSource
import io.reactivex.Observable
import io.reactivex.Single
abstract class VectorViewModel<S : MavericksState, VA : VectorViewModelAction, VE : VectorViewEvents>(initialState: S) : abstract class VectorViewModel<S : MavericksState, VA : VectorViewModelAction, VE : VectorViewEvents>(initialState: S) :
BaseMvRxViewModel<S>(initialState) { MavericksViewModel<S>(initialState) {
interface Factory<S : MavericksState> {
fun create(state: S): BaseMvRxViewModel<S>
}
// Used to post transient events to the View // Used to post transient events to the View
protected val _viewEvents = PublishDataSource<VE>() protected val _viewEvents = PublishDataSource<VE>()
val viewEvents: DataSource<VE> = _viewEvents val viewEvents: DataSource<VE> = _viewEvents
/**
* This method does the same thing as the execute function, but it doesn't subscribe to the stream
* so you can use this in a switchMap or a flatMap
*/
// False positive
@Suppress("USELESS_CAST", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER")
fun <T> Single<T>.toAsync(stateReducer: S.(Async<T>) -> S): Single<Async<T>> {
setState { stateReducer(Loading()) }
return map { Success(it) as Async<T> }
.onErrorReturn { Fail(it) }
.doOnSuccess { setState { stateReducer(it) } }
}
/**
* This method does the same thing as the execute function, but it doesn't subscribe to the stream
* so you can use this in a switchMap or a flatMap
*/
// False positive
@Suppress("USELESS_CAST", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER")
fun <T> Observable<T>.toAsync(stateReducer: S.(Async<T>) -> S): Observable<Async<T>> {
setState { stateReducer(Loading()) }
return map { Success(it) as Async<T> }
.onErrorReturn { Fail(it) }
.doOnNext { setState { stateReducer(it) } }
}
abstract fun handle(action: VA) abstract fun handle(action: VA)
} }

View file

@ -1,43 +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.app.core.rx
import im.vector.app.features.settings.VectorPreferences
import io.reactivex.plugins.RxJavaPlugins
import timber.log.Timber
import javax.inject.Inject
class RxConfig @Inject constructor(
private val vectorPreferences: VectorPreferences
) {
/**
* Make sure unhandled Rx error does not crash the app in production
*/
fun setupRxPlugin() {
RxJavaPlugins.setErrorHandler { throwable ->
Timber.e(throwable, "RxError")
// is InterruptedException -> fine, some blocking code was interrupted by a dispose call
if (throwable !is InterruptedException) {
// Avoid crash in production, except if user wants it
if (vectorPreferences.failFast()) {
throw throwable
}
}
}
}
}

View file

@ -16,23 +16,36 @@
package im.vector.app.core.utils package im.vector.app.core.utils
import io.reactivex.Observable import im.vector.app.core.flow.tickerFlow
import java.util.concurrent.TimeUnit import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
class CountUpTimer(private val intervalInMs: Long = 1_000) { class CountUpTimer(private val intervalInMs: Long = 1_000) {
private val coroutineScope = CoroutineScope(Dispatchers.Main)
private val elapsedTime: AtomicLong = AtomicLong() private val elapsedTime: AtomicLong = AtomicLong()
private val resumed: AtomicBoolean = AtomicBoolean(false) private val resumed: AtomicBoolean = AtomicBoolean(false)
private val disposable = Observable.interval(intervalInMs / 10, TimeUnit.MILLISECONDS) init {
.filter { resumed.get() } startCounter()
.map { elapsedTime.addAndGet(intervalInMs / 10) } }
.filter { it % intervalInMs == 0L }
.subscribe { private fun startCounter() {
tickListener?.onTick(it) tickerFlow(coroutineScope, intervalInMs / 10)
} .filter { resumed.get() }
.map { elapsedTime.addAndGet(intervalInMs / 10) }
.filter { it % intervalInMs == 0L }
.onEach {
tickListener?.onTick(it)
}.launchIn(coroutineScope)
}
var tickListener: TickListener? = null var tickListener: TickListener? = null
@ -49,7 +62,7 @@ class CountUpTimer(private val intervalInMs: Long = 1_000) {
} }
fun stop() { fun stop() {
disposable.dispose() coroutineScope.cancel()
} }
interface TickListener { interface TickListener {

View file

@ -16,13 +16,12 @@
package im.vector.app.core.utils package im.vector.app.core.utils
import com.jakewharton.rxrelay2.BehaviorRelay import kotlinx.coroutines.channels.BufferOverflow
import com.jakewharton.rxrelay2.PublishRelay import kotlinx.coroutines.flow.Flow
import io.reactivex.Observable import kotlinx.coroutines.flow.MutableSharedFlow
import io.reactivex.android.schedulers.AndroidSchedulers
interface DataSource<T> { interface DataSource<T> {
fun observe(): Observable<T> fun stream(): Flow<T>
} }
interface MutableDataSource<T> : DataSource<T> { interface MutableDataSource<T> : DataSource<T> {
@ -34,25 +33,17 @@ interface MutableDataSource<T> : DataSource<T> {
*/ */
open class BehaviorDataSource<T>(private val defaultValue: T? = null) : MutableDataSource<T> { open class BehaviorDataSource<T>(private val defaultValue: T? = null) : MutableDataSource<T> {
private val behaviorRelay = createRelay() private val mutableFlow = MutableSharedFlow<T>(replay = 1)
val currentValue: T? val currentValue: T?
get() = behaviorRelay.value get() = mutableFlow.replayCache.firstOrNull()
override fun observe(): Observable<T> { override fun stream(): Flow<T> {
return behaviorRelay.hide().observeOn(AndroidSchedulers.mainThread()) return mutableFlow
} }
override fun post(value: T) { override fun post(value: T) {
behaviorRelay.accept(value!!) mutableFlow.tryEmit(value)
}
private fun createRelay(): BehaviorRelay<T> {
return if (defaultValue == null) {
BehaviorRelay.create()
} else {
BehaviorRelay.createDefault(defaultValue)
}
} }
} }
@ -61,13 +52,13 @@ open class BehaviorDataSource<T>(private val defaultValue: T? = null) : MutableD
*/ */
open class PublishDataSource<T> : MutableDataSource<T> { open class PublishDataSource<T> : MutableDataSource<T> {
private val publishRelay = PublishRelay.create<T>() private val mutableFlow = MutableSharedFlow<T>(replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override fun observe(): Observable<T> { override fun stream(): Flow<T> {
return publishRelay.hide().observeOn(AndroidSchedulers.mainThread()) return mutableFlow
} }
override fun post(value: T) { override fun post(value: T) {
publishRelay.accept(value!!) mutableFlow.tryEmit(value)
} }
} }

View file

@ -1,31 +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.app.core.utils
import io.reactivex.Completable
import io.reactivex.Single
import io.reactivex.disposables.Disposable
import io.reactivex.internal.functions.Functions
import timber.log.Timber
fun <T> Single<T>.subscribeLogError(): Disposable {
return subscribe(Functions.emptyConsumer(), { Timber.e(it) })
}
fun Completable.subscribeLogError(): Disposable {
return subscribe({}, { Timber.e(it) })
}

View file

@ -39,7 +39,7 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetC
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
callViewModel.subscribe(this) { callViewModel.onEach {
renderState(it) renderState(it)
} }

View file

@ -35,6 +35,7 @@ import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.viewModel
@ -61,7 +62,8 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.RoomDetailArgs import im.vector.app.features.home.room.detail.RoomDetailArgs
import io.github.hyuwah.draggableviewlib.DraggableView import io.github.hyuwah.draggableviewlib.DraggableView
import io.github.hyuwah.draggableviewlib.setupDraggable import io.github.hyuwah.draggableviewlib.setupDraggable
import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.logger.LoggerTag
@ -130,7 +132,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
setSupportActionBar(views.callToolbar) setSupportActionBar(views.callToolbar)
configureCallViews() configureCallViews()
callViewModel.subscribe(this) { callViewModel.onEach {
renderState(it) renderState(it)
} }
@ -141,12 +143,11 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
} }
callViewModel.viewEvents callViewModel.viewEvents
.observe() .stream()
.observeOn(AndroidSchedulers.mainThread()) .onEach {
.subscribe {
handleViewEvents(it) handleViewEvents(it)
} }
.disposeOnDestroy() .launchIn(lifecycleScope)
callViewModel.onEach(VectorCallViewState::callId, VectorCallViewState::isVideoCall) { _, isVideoCall -> callViewModel.onEach(VectorCallViewState::callId, VectorCallViewState::isVideoCall) { _, isVideoCall ->
if (isVideoCall) { if (isVideoCall) {

View file

@ -68,7 +68,7 @@ class VectorJitsiActivity : VectorBaseActivity<ActivityJitsiBinding>(), JitsiMee
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
jitsiViewModel.subscribe(this) { jitsiViewModel.onEach {
renderState(it) renderState(it)
} }

View file

@ -19,8 +19,10 @@ package im.vector.app.features.call.webrtc
import android.content.Context import android.content.Context
import android.hardware.camera2.CameraManager import android.hardware.camera2.CameraManager
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import im.vector.app.core.flow.chunk
import im.vector.app.core.services.CallService import im.vector.app.core.services.CallService
import im.vector.app.core.utils.CountUpTimer import im.vector.app.core.utils.CountUpTimer
import im.vector.app.core.utils.PublishDataSource
import im.vector.app.core.utils.TextUtils.formatDuration import im.vector.app.core.utils.TextUtils.formatDuration
import im.vector.app.features.call.CameraEventsHandlerAdapter import im.vector.app.features.call.CameraEventsHandlerAdapter
import im.vector.app.features.call.CameraProxy import im.vector.app.features.call.CameraProxy
@ -35,14 +37,16 @@ import im.vector.app.features.call.utils.awaitSetLocalDescription
import im.vector.app.features.call.utils.awaitSetRemoteDescription import im.vector.app.features.call.utils.awaitSetRemoteDescription
import im.vector.app.features.call.utils.mapToCallCandidate import im.vector.app.features.call.utils.mapToCallCandidate
import im.vector.app.features.session.coroutineScope import im.vector.app.features.session.coroutineScope
import io.reactivex.disposables.Disposable
import io.reactivex.subjects.PublishSubject
import io.reactivex.subjects.ReplaySubject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
@ -85,7 +89,6 @@ import org.webrtc.VideoTrack
import timber.log.Timber import timber.log.Timber
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit
import javax.inject.Provider import javax.inject.Provider
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -157,7 +160,7 @@ class WebRtcCall(
private var currentCaptureFormat: CaptureFormat = CaptureFormat.HD private var currentCaptureFormat: CaptureFormat = CaptureFormat.HD
private var cameraAvailabilityCallback: CameraManager.AvailabilityCallback? = null private var cameraAvailabilityCallback: CameraManager.AvailabilityCallback? = null
private val timer = CountUpTimer(Duration.ofSeconds(1).toMillis()).apply { private val timer = CountUpTimer(1000L).apply {
tickListener = object : CountUpTimer.TickListener { tickListener = object : CountUpTimer.TickListener {
override fun onTick(milliseconds: Long) { override fun onTick(milliseconds: Long) {
val formattedDuration = formatDuration(Duration.ofMillis(milliseconds)) val formattedDuration = formatDuration(Duration.ofMillis(milliseconds))
@ -197,26 +200,33 @@ class WebRtcCall(
private var localSurfaceRenderers: MutableList<WeakReference<SurfaceViewRenderer>> = ArrayList() private var localSurfaceRenderers: MutableList<WeakReference<SurfaceViewRenderer>> = ArrayList()
private var remoteSurfaceRenderers: MutableList<WeakReference<SurfaceViewRenderer>> = ArrayList() private var remoteSurfaceRenderers: MutableList<WeakReference<SurfaceViewRenderer>> = ArrayList()
private val iceCandidateSource: PublishSubject<IceCandidate> = PublishSubject.create() private val localIceCandidateSource = PublishDataSource<IceCandidate>()
private val iceCandidateDisposable = iceCandidateSource private var localIceCandidateJob: Job? = null
.buffer(300, TimeUnit.MILLISECONDS)
.subscribe {
// omit empty :/
if (it.isNotEmpty()) {
Timber.tag(loggerTag.value).v("Sending local ice candidates to call")
// it.forEach { peerConnection?.addIceCandidate(it) }
mxCall.sendLocalCallCandidates(it.mapToCallCandidate())
}
}
private val remoteCandidateSource: ReplaySubject<IceCandidate> = ReplaySubject.create() private val remoteCandidateSource: MutableSharedFlow<IceCandidate> = MutableSharedFlow(replay = Int.MAX_VALUE)
private var remoteIceCandidateDisposable: Disposable? = null private var remoteIceCandidateJob: Job? = null
init { init {
setupLocalIceCanditate()
mxCall.addListener(this) mxCall.addListener(this)
} }
fun onIceCandidate(iceCandidate: IceCandidate) = iceCandidateSource.onNext(iceCandidate) private fun setupLocalIceCanditate() {
sessionScope?.let {
localIceCandidateJob = localIceCandidateSource.stream()
.chunk(300)
.onEach {
// omit empty :/
if (it.isNotEmpty()) {
Timber.tag(loggerTag.value).v("Sending local ice candidates to call")
// it.forEach { peerConnection?.addIceCandidate(it) }
mxCall.sendLocalCallCandidates(it.mapToCallCandidate())
}
}.launchIn(it)
}
}
fun onIceCandidate(iceCandidate: IceCandidate) = localIceCandidateSource.post(iceCandidate)
fun onRenegotiationNeeded(restartIce: Boolean) { fun onRenegotiationNeeded(restartIce: Boolean) {
sessionScope?.launch(dispatcher) { sessionScope?.launch(dispatcher) {
@ -438,12 +448,15 @@ class WebRtcCall(
createLocalStream() createLocalStream()
attachViewRenderersInternal() attachViewRenderersInternal()
Timber.tag(loggerTag.value).v("remoteCandidateSource $remoteCandidateSource") Timber.tag(loggerTag.value).v("remoteCandidateSource $remoteCandidateSource")
remoteIceCandidateDisposable = remoteCandidateSource.subscribe({ remoteIceCandidateJob = remoteCandidateSource
Timber.tag(loggerTag.value).v("adding remote ice candidate $it") .onEach {
peerConnection?.addIceCandidate(it) Timber.tag(loggerTag.value).v("adding remote ice candidate $it")
}, { peerConnection?.addIceCandidate(it)
Timber.tag(loggerTag.value).v("failed to add remote ice candidate $it") }
}) .catch {
Timber.tag(loggerTag.value).v("failed to add remote ice candidate $it")
}
.launchIn(this)
// Now we wait for negotiation callback // Now we wait for negotiation callback
} }
@ -488,12 +501,13 @@ class WebRtcCall(
mxCall.accept(it.description) mxCall.accept(it.description)
} }
Timber.tag(loggerTag.value).v("remoteCandidateSource $remoteCandidateSource") Timber.tag(loggerTag.value).v("remoteCandidateSource $remoteCandidateSource")
remoteIceCandidateDisposable = remoteCandidateSource.subscribe({ remoteIceCandidateJob = remoteCandidateSource
Timber.tag(loggerTag.value).v("adding remote ice candidate $it") .onEach {
peerConnection?.addIceCandidate(it) Timber.tag(loggerTag.value).v("adding remote ice candidate $it")
}, { peerConnection?.addIceCandidate(it)
Timber.tag(loggerTag.value).v("failed to add remote ice candidate $it") }.catch {
}) Timber.tag(loggerTag.value).v("failed to add remote ice candidate $it")
}.launchIn(this)
} }
private suspend fun getTurnServer(): TurnServerResponse? { private suspend fun getTurnServer(): TurnServerResponse? {
@ -761,8 +775,8 @@ class WebRtcCall(
videoCapturer?.stopCapture() videoCapturer?.stopCapture()
videoCapturer?.dispose() videoCapturer?.dispose()
videoCapturer = null videoCapturer = null
remoteIceCandidateDisposable?.dispose() remoteIceCandidateJob?.cancel()
iceCandidateDisposable?.dispose() localIceCandidateJob?.cancel()
peerConnection?.close() peerConnection?.close()
peerConnection?.dispose() peerConnection?.dispose()
localAudioSource?.dispose() localAudioSource?.dispose()
@ -852,7 +866,7 @@ class WebRtcCall(
} }
Timber.tag(loggerTag.value).v("onCallIceCandidateReceived for call ${mxCall.callId} sdp: ${it.candidate}") Timber.tag(loggerTag.value).v("onCallIceCandidateReceived for call ${mxCall.callId} sdp: ${it.candidate}")
val iceCandidate = IceCandidate(it.sdpMid, it.sdpMLineIndex, it.candidate) val iceCandidate = IceCandidate(it.sdpMid, it.sdpMLineIndex, it.candidate)
remoteCandidateSource.onNext(iceCandidate) remoteCandidateSource.emit(iceCandidate)
} }
} }
} }

View file

@ -21,10 +21,9 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.checkedChanges
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
@ -37,9 +36,13 @@ import im.vector.app.features.userdirectory.UserListAction
import im.vector.app.features.userdirectory.UserListSharedAction import im.vector.app.features.userdirectory.UserListSharedAction
import im.vector.app.features.userdirectory.UserListSharedActionViewModel import im.vector.app.features.userdirectory.UserListSharedActionViewModel
import im.vector.app.features.userdirectory.UserListViewModel import im.vector.app.features.userdirectory.UserListViewModel
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.session.user.model.User
import java.util.concurrent.TimeUnit import reactivecircus.flowbinding.android.widget.checkedChanges
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
class ContactsBookFragment @Inject constructor( class ContactsBookFragment @Inject constructor(
@ -83,21 +86,21 @@ class ContactsBookFragment @Inject constructor(
private fun setupOnlyBoundContactsView() { private fun setupOnlyBoundContactsView() {
views.phoneBookOnlyBoundContacts.checkedChanges() views.phoneBookOnlyBoundContacts.checkedChanges()
.subscribe { .onEach {
contactsBookViewModel.handle(ContactsBookAction.OnlyBoundContacts(it)) contactsBookViewModel.handle(ContactsBookAction.OnlyBoundContacts(it))
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun setupFilterView() { private fun setupFilterView() {
views.phoneBookFilter views.phoneBookFilter
.textChanges() .textChanges()
.skipInitialValue() .skipInitialValue()
.debounce(300, TimeUnit.MILLISECONDS) .debounce(300)
.subscribe { .onEach {
contactsBookViewModel.handle(ContactsBookAction.FilterWith(it.toString())) contactsBookViewModel.handle(ContactsBookAction.FilterWith(it.toString()))
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
override fun onDestroyView() { override fun onDestroyView() {

View file

@ -22,6 +22,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
@ -46,6 +47,8 @@ import im.vector.app.features.userdirectory.UserListFragment
import im.vector.app.features.userdirectory.UserListFragmentArgs import im.vector.app.features.userdirectory.UserListFragmentArgs
import im.vector.app.features.userdirectory.UserListSharedAction import im.vector.app.features.userdirectory.UserListSharedAction
import im.vector.app.features.userdirectory.UserListSharedActionViewModel import im.vector.app.features.userdirectory.UserListSharedActionViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import java.net.HttpURLConnection import java.net.HttpURLConnection
@ -64,8 +67,8 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
sharedActionViewModel sharedActionViewModel
.observe() .stream()
.subscribe { action -> .onEach { action ->
when (action) { when (action) {
UserListSharedAction.Close -> finish() UserListSharedAction.Close -> finish()
UserListSharedAction.GoBack -> onBackPressed() UserListSharedAction.GoBack -> onBackPressed()
@ -74,7 +77,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
UserListSharedAction.AddByQrCode -> openAddByQrCode() UserListSharedAction.AddByQrCode -> openAddByQrCode()
}.exhaustive }.exhaustive
} }
.disposeOnDestroy() .launchIn(lifecycleScope)
if (isFirstCreation()) { if (isFirstCreation()) {
addFragment( addFragment(
R.id.container, R.id.container,

View file

@ -63,7 +63,7 @@ class SharedSecureStorageActivity :
viewModel.observeViewEvents { observeViewEvents(it) } viewModel.observeViewEvents { observeViewEvents(it) }
viewModel.subscribe(this) { renderState(it) } viewModel.onEach { renderState(it) }
} }
override fun onDestroy() { override fun onDestroy() {

View file

@ -22,17 +22,19 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.startImportTextFromFileIntent import im.vector.app.core.utils.startImportTextFromFileIntent
import im.vector.app.databinding.FragmentSsssAccessFromKeyBinding import im.vector.app.databinding.FragmentSsssAccessFromKeyBinding
import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import java.util.concurrent.TimeUnit import reactivecircus.flowbinding.android.widget.editorActionEvents
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment<FragmentSsssAccessFromKeyBinding>() { class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment<FragmentSsssAccessFromKeyBinding>() {
@ -48,22 +50,21 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
views.ssssRestoreWithKeyText.text = getString(R.string.enter_secret_storage_input_key) views.ssssRestoreWithKeyText.text = getString(R.string.enter_secret_storage_input_key)
views.ssssKeyEnterEdittext.editorActionEvents() views.ssssKeyEnterEdittext.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300)
.observeOn(AndroidSchedulers.mainThread()) .onEach {
.subscribe {
if (it.actionId == EditorInfo.IME_ACTION_DONE) { if (it.actionId == EditorInfo.IME_ACTION_DONE) {
submit() submit()
} }
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
views.ssssKeyEnterEdittext.textChanges() views.ssssKeyEnterEdittext.textChanges()
.skipInitialValue() .skipInitialValue()
.subscribe { .onEach {
views.ssssKeyEnterTil.error = null views.ssssKeyEnterTil.error = null
views.ssssKeySubmit.isEnabled = it.isNotBlank() views.ssssKeySubmit.isEnabled = it.isNotBlank()
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
views.ssssKeyUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) } views.ssssKeyUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }

View file

@ -22,15 +22,17 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding
import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.flow.launchIn
import java.util.concurrent.TimeUnit import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.widget.editorActionEvents
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
class SharedSecuredStoragePassphraseFragment @Inject constructor( class SharedSecuredStoragePassphraseFragment @Inject constructor(
@ -60,21 +62,20 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
// .colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) // .colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
views.ssssPassphraseEnterEdittext.editorActionEvents() views.ssssPassphraseEnterEdittext.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300)
.observeOn(AndroidSchedulers.mainThread()) .onEach {
.subscribe {
if (it.actionId == EditorInfo.IME_ACTION_DONE) { if (it.actionId == EditorInfo.IME_ACTION_DONE) {
submit() submit()
} }
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
views.ssssPassphraseEnterEdittext.textChanges() views.ssssPassphraseEnterEdittext.textChanges()
.subscribe { .onEach {
views.ssssPassphraseEnterTil.error = null views.ssssPassphraseEnterTil.error = null
views.ssssPassphraseSubmit.isEnabled = it.isNotBlank() views.ssssPassphraseSubmit.isEnabled = it.isNotBlank()
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
views.ssssPassphraseReset.views.bottomSheetActionClickableZone.debouncedClicks { views.ssssPassphraseReset.views.bottomSheetActionClickableZone.debouncedClicks {
sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll) sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll)

View file

@ -55,7 +55,7 @@ class SharedSecuredStorageResetAllFragment @Inject constructor() :
} }
} }
sharedViewModel.subscribe(this) { state -> sharedViewModel.onEach { state ->
views.ssssResetOtherDevices.setTextOrHide( views.ssssResetOtherDevices.setTextOrHide(
state.activeDeviceCount state.activeDeviceCount
.takeIf { it > 0 } .takeIf { it > 0 }

View file

@ -22,16 +22,18 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.flow.launchIn
import java.util.concurrent.TimeUnit import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.widget.editorActionEvents
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
class BootstrapConfirmPassphraseFragment @Inject constructor() : class BootstrapConfirmPassphraseFragment @Inject constructor() :
@ -58,21 +60,20 @@ class BootstrapConfirmPassphraseFragment @Inject constructor() :
} }
views.ssssPassphraseEnterEdittext.editorActionEvents() views.ssssPassphraseEnterEdittext.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300)
.observeOn(AndroidSchedulers.mainThread()) .onEach {
.subscribe {
if (it.actionId == EditorInfo.IME_ACTION_DONE) { if (it.actionId == EditorInfo.IME_ACTION_DONE) {
submit() submit()
} }
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
views.ssssPassphraseEnterEdittext.textChanges() views.ssssPassphraseEnterEdittext.textChanges()
.subscribe { .onEach {
views.ssssPassphraseEnterTil.error = null views.ssssPassphraseEnterTil.error = null
sharedViewModel.handle(BootstrapActions.UpdateConfirmCandidatePassphrase(it?.toString() ?: "")) sharedViewModel.handle(BootstrapActions.UpdateConfirmCandidatePassphrase(it.toString()))
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
sharedViewModel.observeViewEvents { sharedViewModel.observeViewEvents {
// when (it) { // when (it) {

View file

@ -21,16 +21,18 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorLocale
import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.flow.launchIn
import java.util.concurrent.TimeUnit import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.widget.editorActionEvents
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
class BootstrapEnterPassphraseFragment @Inject constructor() : class BootstrapEnterPassphraseFragment @Inject constructor() :
@ -53,22 +55,21 @@ class BootstrapEnterPassphraseFragment @Inject constructor() :
views.ssssPassphraseEnterEdittext.setText(it.passphrase ?: "") views.ssssPassphraseEnterEdittext.setText(it.passphrase ?: "")
} }
views.ssssPassphraseEnterEdittext.editorActionEvents() views.ssssPassphraseEnterEdittext.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300)
.observeOn(AndroidSchedulers.mainThread()) .onEach {
.subscribe {
if (it.actionId == EditorInfo.IME_ACTION_DONE) { if (it.actionId == EditorInfo.IME_ACTION_DONE) {
submit() submit()
} }
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
views.ssssPassphraseEnterEdittext.textChanges() views.ssssPassphraseEnterEdittext.textChanges()
.subscribe { .onEach {
// ssss_passphrase_enter_til.error = null // ssss_passphrase_enter_til.error = null
sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: "")) sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it.toString()))
// ssss_passphrase_submit.isEnabled = it.isNotBlank() // ssss_passphrase_submit.isEnabled = it.isNotBlank()
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
sharedViewModel.observeViewEvents { sharedViewModel.observeViewEvents {
// when (it) { // when (it) {

View file

@ -27,22 +27,24 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.core.utils.startImportTextFromFileIntent import im.vector.app.core.utils.startImportTextFromFileIntent
import im.vector.app.databinding.FragmentBootstrapMigrateBackupBinding import im.vector.app.databinding.FragmentBootstrapMigrateBackupBinding
import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey
import java.util.concurrent.TimeUnit import reactivecircus.flowbinding.android.widget.editorActionEvents
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
class BootstrapMigrateBackupFragment @Inject constructor( class BootstrapMigrateBackupFragment @Inject constructor(
@ -63,22 +65,21 @@ class BootstrapMigrateBackupFragment @Inject constructor(
views.bootstrapMigrateEditText.setText(it.passphrase ?: "") views.bootstrapMigrateEditText.setText(it.passphrase ?: "")
} }
views.bootstrapMigrateEditText.editorActionEvents() views.bootstrapMigrateEditText.editorActionEvents()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300)
.observeOn(AndroidSchedulers.mainThread()) .onEach {
.subscribe {
if (it.actionId == EditorInfo.IME_ACTION_DONE) { if (it.actionId == EditorInfo.IME_ACTION_DONE) {
submit() submit()
} }
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
views.bootstrapMigrateEditText.textChanges() views.bootstrapMigrateEditText.textChanges()
.skipInitialValue() .skipInitialValue()
.subscribe { .onEach {
views.bootstrapRecoveryKeyEnterTil.error = null views.bootstrapRecoveryKeyEnterTil.error = null
// sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: "")) // sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: ""))
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
// sharedViewModel.observeViewEvents {} // sharedViewModel.observeViewEvents {}
views.bootstrapMigrateContinueButton.debouncedClicks { submit() } views.bootstrapMigrateContinueButton.debouncedClicks { submit() }

View file

@ -66,7 +66,7 @@ class RoomDevToolActivity : SimpleFragmentActivity(), FragmentManager.OnBackStac
override fun initUiAndData() { override fun initUiAndData() {
super.initUiAndData() super.initUiAndData()
viewModel.subscribe(this) { viewModel.onEach {
renderState(it) renderState(it)
} }

View file

@ -20,12 +20,15 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentDevtoolsEditorBinding import im.vector.app.databinding.FragmentDevtoolsEditorBinding
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
class RoomDevToolEditFragment @Inject constructor() : class RoomDevToolEditFragment @Inject constructor() :
@ -44,10 +47,10 @@ class RoomDevToolEditFragment @Inject constructor() :
} }
views.editText.textChanges() views.editText.textChanges()
.skipInitialValue() .skipInitialValue()
.subscribe { .onEach {
sharedViewModel.handle(RoomDevToolAction.UpdateContentText(it.toString())) sharedViewModel.handle(RoomDevToolAction.UpdateContentText(it.toString()))
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
override fun onResume() { override fun onResume() {

View file

@ -24,10 +24,10 @@ import android.view.inputmethod.EditorInfo
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.registerStartForActivityResult
@ -37,7 +37,10 @@ import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.databinding.FragmentSetIdentityServerBinding import im.vector.app.databinding.FragmentSetIdentityServerBinding
import im.vector.app.features.discovery.DiscoverySharedViewModel import im.vector.app.features.discovery.DiscoverySharedViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.api.session.terms.TermsService
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
class SetIdentityServerFragment @Inject constructor( class SetIdentityServerFragment @Inject constructor(
@ -90,11 +93,11 @@ class SetIdentityServerFragment @Inject constructor(
views.identityServerSetDefaultAlternativeTextInput views.identityServerSetDefaultAlternativeTextInput
.textChanges() .textChanges()
.subscribe { .onEach {
views.identityServerSetDefaultAlternativeTil.error = null views.identityServerSetDefaultAlternativeTil.error = null
views.identityServerSetDefaultAlternativeSubmit.isEnabled = it.isNotEmpty() views.identityServerSetDefaultAlternativeSubmit.isEnabled = it.isNotEmpty()
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
views.identityServerSetDefaultSubmit.debouncedClicks { views.identityServerSetDefaultSubmit.debouncedClicks {
viewModel.handle(SetIdentityServerAction.UseDefaultIdentityServer) viewModel.handle(SetIdentityServerAction.UseDefaultIdentityServer)

View file

@ -73,6 +73,8 @@ import im.vector.app.features.spaces.share.ShareSpaceBottomSheet
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
import im.vector.app.push.fcm.FcmHelper import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.initsync.SyncStatusService import org.matrix.android.sdk.api.session.initsync.SyncStatusService
@ -178,8 +180,8 @@ class HomeActivity :
} }
sharedActionViewModel sharedActionViewModel
.observe() .stream()
.subscribe { sharedAction -> .onEach { sharedAction ->
when (sharedAction) { when (sharedAction) {
is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START) is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START)
is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START) is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START)
@ -222,7 +224,7 @@ class HomeActivity :
} }
}.exhaustive }.exhaustive
} }
.disposeOnDestroy() .launchIn(lifecycleScope)
val args = intent.getParcelableExtra<HomeActivityArgs>(Mavericks.KEY_ARG) val args = intent.getParcelableExtra<HomeActivityArgs>(Mavericks.KEY_ARG)
@ -243,13 +245,12 @@ class HomeActivity :
is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
}.exhaustive }.exhaustive
} }
homeActivityViewModel.subscribe(this) { renderState(it) } homeActivityViewModel.onEach { renderState(it) }
shortcutsHandler.observeRoomsAndBuildShortcuts() shortcutsHandler.observeRoomsAndBuildShortcuts(lifecycleScope)
.disposeOnDestroy()
if (!vectorPreferences.didPromoteNewRestrictedFeature()) { if (!vectorPreferences.didPromoteNewRestrictedFeature()) {
promoteRestrictedViewModel.subscribe(this) { promoteRestrictedViewModel.onEach {
if (it.activeSpaceSummary != null && !it.activeSpaceSummary.isPublic && if (it.activeSpaceSummary != null && !it.activeSpaceSummary.isPublic &&
it.activeSpaceSummary.otherMemberIds.isNotEmpty()) { it.activeSpaceSummary.otherMemberIds.isNotEmpty()) {
// It's a private space with some members show this once // It's a private space with some members show this once

View file

@ -299,7 +299,7 @@ class HomeDetailFragment @Inject constructor(
private fun setupKeysBackupBanner() { private fun setupKeysBackupBanner() {
serverBackupStatusViewModel serverBackupStatusViewModel
.subscribe(this) { .onEach {
when (val banState = it.bannerState.invoke()) { when (val banState = it.bannerState.invoke()) {
is BannerState.Setup -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false) is BannerState.Setup -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false)
BannerState.BackingUp -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false) BannerState.BackingUp -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false)

View file

@ -27,6 +27,7 @@ import im.vector.app.RoomGroupingMethod
import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.call.dialpad.DialPadLookup import im.vector.app.features.call.dialpad.DialPadLookup
import im.vector.app.features.call.lookup.CallProtocolsChecker import im.vector.app.features.call.lookup.CallProtocolsChecker
@ -36,10 +37,13 @@ import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.showInvites import im.vector.app.features.invite.showInvites
import im.vector.app.features.settings.VectorDataStore import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.ui.UiStateRepository import im.vector.app.features.ui.UiStateRepository
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter
@ -50,9 +54,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.rx.asObservable
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
/** /**
* View model used to update the home bottom bar notification counts, observe the sync state and * View model used to update the home bottom bar notification counts, observe the sync state and
@ -66,7 +68,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
private val directRoomHelper: DirectRoomHelper, private val directRoomHelper: DirectRoomHelper,
private val appStateHandler: AppStateHandler, private val appStateHandler: AppStateHandler,
private val autoAcceptInvites: AutoAcceptInvites) : private val autoAcceptInvites: AutoAcceptInvites) :
VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState), VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState),
CallProtocolsChecker.Listener { CallProtocolsChecker.Listener {
@AssistedFactory @AssistedFactory
@ -194,18 +196,15 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
private fun observeRoomGroupingMethod() { private fun observeRoomGroupingMethod() {
appStateHandler.selectedRoomGroupingObservable appStateHandler.selectedRoomGroupingObservable
.subscribe { .setOnEach {
setState { copy(
copy( roomGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null)
roomGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null) )
)
}
} }
.disposeOnClear()
} }
private fun observeRoomSummaries() { private fun observeRoomSummaries() {
appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged().switchMap { appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged().flatMapLatest {
// we use it as a trigger to all changes in room, but do not really load // we use it as a trigger to all changes in room, but do not really load
// the actual models // the actual models
session.getPagedRoomSummariesLive( session.getPagedRoomSummariesLive(
@ -213,11 +212,10 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
memberships = Membership.activeMemberships() memberships = Membership.activeMemberships()
}, },
sortOrder = RoomSortOrder.NONE sortOrder = RoomSortOrder.NONE
).asObservable() ).asFlow()
} }
.observeOn(Schedulers.computation()) .throttleFirst(300)
.throttleFirst(300, TimeUnit.MILLISECONDS) .onEach {
.subscribe {
when (val groupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) { when (val groupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) {
is RoomGroupingMethod.ByLegacyGroup -> { is RoomGroupingMethod.ByLegacyGroup -> {
// TODO!! // TODO!!
@ -274,6 +272,6 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
} }
} }
} }
.disposeOnClear() .launchIn(viewModelScope)
} }
} }

View file

@ -29,6 +29,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.flow.distinctUntilChanged
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel

View file

@ -22,54 +22,62 @@ import android.os.Build
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.dispatchers.CoroutineDispatchers
import im.vector.app.features.pin.PinCodeStore import im.vector.app.features.pin.PinCodeStore
import im.vector.app.features.pin.PinCodeStoreListener import im.vector.app.features.pin.PinCodeStoreListener
import io.reactivex.disposables.Disposable import kotlinx.coroutines.CoroutineScope
import io.reactivex.disposables.Disposables import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.flow.flow
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject import javax.inject.Inject
class ShortcutsHandler @Inject constructor( class ShortcutsHandler @Inject constructor(
private val context: Context, private val context: Context,
private val appDispatchers: CoroutineDispatchers,
private val shortcutCreator: ShortcutCreator, private val shortcutCreator: ShortcutCreator,
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val pinCodeStore: PinCodeStore private val pinCodeStore: PinCodeStore
) : PinCodeStoreListener { ) : PinCodeStoreListener {
private val isRequestPinShortcutSupported = ShortcutManagerCompat.isRequestPinShortcutSupported(context) private val isRequestPinShortcutSupported = ShortcutManagerCompat.isRequestPinShortcutSupported(context)
// Value will be set correctly if necessary // Value will be set correctly if necessary
private var hasPinCode = true private var hasPinCode = AtomicBoolean(true)
fun observeRoomsAndBuildShortcuts(): Disposable { fun observeRoomsAndBuildShortcuts(coroutineScope: CoroutineScope): Job {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
// No op // No op
return Disposables.empty() return Job()
} }
hasPinCode.set(pinCodeStore.getEncodedPin() != null)
hasPinCode = pinCodeStore.getEncodedPin() != null val session = activeSessionHolder.getSafeActiveSession() ?: return Job()
return session.flow().liveRoomSummaries(
val session = activeSessionHolder.getSafeActiveSession() ?: return Disposables.empty()
return session.getRoomSummariesLive(
roomSummaryQueryParams { roomSummaryQueryParams {
memberships = listOf(Membership.JOIN) memberships = listOf(Membership.JOIN)
}, },
sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY
) )
.asObservable() .onStart { pinCodeStore.addListener(this@ShortcutsHandler) }
.doOnSubscribe { pinCodeStore.addListener(this) } .onCompletion { pinCodeStore.removeListener(this@ShortcutsHandler) }
.doFinally { pinCodeStore.removeListener(this) } .onEach { rooms ->
.subscribe { rooms ->
// Remove dead shortcuts (i.e. deleted rooms) // Remove dead shortcuts (i.e. deleted rooms)
removeDeadShortcut(rooms.map { it.roomId }) removeDeadShortcut(rooms.map { it.roomId })
// Create shortcuts // Create shortcuts
createShortcuts(rooms) createShortcuts(rooms)
} }
.flowOn(appDispatchers.computation)
.launchIn(coroutineScope)
} }
private fun removeDeadShortcut(roomIds: List<String>) { private fun removeDeadShortcut(roomIds: List<String>) {
@ -89,7 +97,7 @@ class ShortcutsHandler @Inject constructor(
} }
private fun createShortcuts(rooms: List<RoomSummary>) { private fun createShortcuts(rooms: List<RoomSummary>) {
if (hasPinCode) { if (hasPinCode.get()) {
// No shortcut in this case (privacy) // No shortcut in this case (privacy)
ShortcutManagerCompat.removeAllDynamicShortcuts(context) ShortcutManagerCompat.removeAllDynamicShortcuts(context)
} else { } else {
@ -127,7 +135,7 @@ class ShortcutsHandler @Inject constructor(
} }
override fun onPinSetUpChange(isConfigured: Boolean) { override fun onPinSetUpChange(isConfigured: Boolean) {
hasPinCode = isConfigured hasPinCode.set(isConfigured)
if (isConfigured) { if (isConfigured) {
// Remove shortcuts immediately // Remove shortcuts immediately
ShortcutManagerCompat.removeAllDynamicShortcuts(context) ShortcutManagerCompat.removeAllDynamicShortcuts(context)

View file

@ -16,6 +16,7 @@
package im.vector.app.features.home package im.vector.app.features.home
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted import dagger.assisted.Assisted
@ -25,13 +26,17 @@ import im.vector.app.AppStateHandler
import im.vector.app.RoomGroupingMethod import im.vector.app.RoomGroupingMethod
import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import io.reactivex.Observable import kotlinx.coroutines.Dispatchers
import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSortOrder
@ -39,8 +44,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
import org.matrix.android.sdk.rx.asObservable
import java.util.concurrent.TimeUnit
data class UnreadMessagesState( data class UnreadMessagesState(
val homeSpaceUnread: RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0), val homeSpaceUnread: RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0),
@ -57,7 +60,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
private val vectorPreferences: VectorPreferences, private val vectorPreferences: VectorPreferences,
appStateHandler: AppStateHandler, appStateHandler: AppStateHandler,
private val autoAcceptInvites: AutoAcceptInvites) : private val autoAcceptInvites: AutoAcceptInvites) :
VectorViewModel<UnreadMessagesState, EmptyAction, EmptyViewEvents>(initialState) { VectorViewModel<UnreadMessagesState, EmptyAction, EmptyViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<UnreadMessagesSharedViewModel, UnreadMessagesState> { interface Factory : MavericksAssistedViewModelFactory<UnreadMessagesSharedViewModel, UnreadMessagesState> {
@ -75,8 +78,8 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
this.memberships = listOf(Membership.JOIN) this.memberships = listOf(Membership.JOIN)
this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null)
}, sortOrder = RoomSortOrder.NONE }, sortOrder = RoomSortOrder.NONE
).asObservable() ).asFlow()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300)
.execute { .execute {
val counts = session.getNotificationCountForRooms( val counts = session.getNotificationCountForRooms(
roomSummaryQueryParams { roomSummaryQueryParams {
@ -103,91 +106,91 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
) )
} }
Observable.combineLatest( combine(
appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged(), appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged(),
appStateHandler.selectedRoomGroupingObservable.switchMap { appStateHandler.selectedRoomGroupingObservable.flatMapLatest {
session.getPagedRoomSummariesLive( session.getPagedRoomSummariesLive(
roomSummaryQueryParams { roomSummaryQueryParams {
this.memberships = Membership.activeMemberships() this.memberships = Membership.activeMemberships()
}, sortOrder = RoomSortOrder.NONE }, sortOrder = RoomSortOrder.NONE
).asObservable() ).asFlow()
.throttleFirst(300, TimeUnit.MILLISECONDS) .throttleFirst(300)
.observeOn(Schedulers.computation())
},
{ groupingMethod, _ ->
when (groupingMethod.orNull()) {
is RoomGroupingMethod.ByLegacyGroup -> {
// currently not supported
CountInfo(
RoomAggregateNotificationCount(0, 0),
RoomAggregateNotificationCount(0, 0)
)
}
is RoomGroupingMethod.BySpace -> {
val selectedSpace = appStateHandler.safeActiveSpaceId()
val inviteCount = if (autoAcceptInvites.hideInvites) {
0
} else {
session.getRoomSummaries(
roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) }
).size
}
val spaceInviteCount = if (autoAcceptInvites.hideInvites) {
0
} else {
session.getRoomSummaries(
spaceSummaryQueryParams {
this.memberships = listOf(Membership.INVITE)
}
).size
}
val totalCount = session.getNotificationCountForRooms(
roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN)
this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf {
!vectorPreferences.prefSpacesShowAllRoomInHome()
} ?: ActiveSpaceFilter.None
}
)
val counts = RoomAggregateNotificationCount(
totalCount.notificationCount + inviteCount,
totalCount.highlightCount + inviteCount
)
val rootCounts = session.spaceService().getRootSpaceSummaries()
.filter {
// filter out current selection
it.roomId != selectedSpace
}
CountInfo(
homeCount = counts,
otherCount = RoomAggregateNotificationCount(
notificationCount = rootCounts.fold(0, { acc, rs -> acc + rs.notificationCount }) +
(counts.notificationCount.takeIf { selectedSpace != null } ?: 0) +
spaceInviteCount,
highlightCount = rootCounts.fold(0, { acc, rs -> acc + rs.highlightCount }) +
(counts.highlightCount.takeIf { selectedSpace != null } ?: 0) +
spaceInviteCount
)
)
}
null -> {
CountInfo(
RoomAggregateNotificationCount(0, 0),
RoomAggregateNotificationCount(0, 0)
)
}
}
} }
).execute { ) { groupingMethod, _ ->
copy( when (groupingMethod.orNull()) {
homeSpaceUnread = it.invoke()?.homeCount ?: RoomAggregateNotificationCount(0, 0), is RoomGroupingMethod.ByLegacyGroup -> {
otherSpacesUnread = it.invoke()?.otherCount ?: RoomAggregateNotificationCount(0, 0) // currently not supported
) CountInfo(
RoomAggregateNotificationCount(0, 0),
RoomAggregateNotificationCount(0, 0)
)
}
is RoomGroupingMethod.BySpace -> {
val selectedSpace = appStateHandler.safeActiveSpaceId()
val inviteCount = if (autoAcceptInvites.hideInvites) {
0
} else {
session.getRoomSummaries(
roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) }
).size
}
val spaceInviteCount = if (autoAcceptInvites.hideInvites) {
0
} else {
session.getRoomSummaries(
spaceSummaryQueryParams {
this.memberships = listOf(Membership.INVITE)
}
).size
}
val totalCount = session.getNotificationCountForRooms(
roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN)
this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf {
!vectorPreferences.prefSpacesShowAllRoomInHome()
} ?: ActiveSpaceFilter.None
}
)
val counts = RoomAggregateNotificationCount(
totalCount.notificationCount + inviteCount,
totalCount.highlightCount + inviteCount
)
val rootCounts = session.spaceService().getRootSpaceSummaries()
.filter {
// filter out current selection
it.roomId != selectedSpace
}
CountInfo(
homeCount = counts,
otherCount = RoomAggregateNotificationCount(
notificationCount = rootCounts.fold(0, { acc, rs -> acc + rs.notificationCount }) +
(counts.notificationCount.takeIf { selectedSpace != null } ?: 0) +
spaceInviteCount,
highlightCount = rootCounts.fold(0, { acc, rs -> acc + rs.highlightCount }) +
(counts.highlightCount.takeIf { selectedSpace != null } ?: 0) +
spaceInviteCount
)
)
}
null -> {
CountInfo(
RoomAggregateNotificationCount(0, 0),
RoomAggregateNotificationCount(0, 0)
)
}
}
} }
.flowOn(Dispatchers.Default)
.execute {
copy(
homeSpaceUnread = it.invoke()?.homeCount ?: RoomAggregateNotificationCount(0, 0),
otherSpacesUnread = it.invoke()?.otherCount ?: RoomAggregateNotificationCount(0, 0)
)
}
} }
} }

View file

@ -24,6 +24,7 @@ import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.viewModel
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
@ -40,6 +41,8 @@ import im.vector.app.features.navigation.Navigator
import im.vector.app.features.room.RequireActiveMembershipAction import im.vector.app.features.room.RequireActiveMembershipAction
import im.vector.app.features.room.RequireActiveMembershipViewEvents import im.vector.app.features.room.RequireActiveMembershipViewEvents
import im.vector.app.features.room.RequireActiveMembershipViewModel import im.vector.app.features.room.RequireActiveMembershipViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@AndroidEntryPoint @AndroidEntryPoint
class RoomDetailActivity : class RoomDetailActivity :
@ -97,13 +100,13 @@ class RoomDetailActivity :
sharedActionViewModel = viewModelProvider.get(RoomDetailSharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(RoomDetailSharedActionViewModel::class.java)
sharedActionViewModel sharedActionViewModel
.observe() .stream()
.subscribe { sharedAction -> .onEach { sharedAction ->
when (sharedAction) { when (sharedAction) {
is RoomDetailSharedAction.SwitchToRoom -> switchToRoom(sharedAction) is RoomDetailSharedAction.SwitchToRoom -> switchToRoom(sharedAction)
} }
} }
.disposeOnDestroy() .launchIn(lifecycleScope)
requireActiveMembershipViewModel.observeViewEvents { requireActiveMembershipViewModel.observeViewEvents {
when (it) { when (it) {

View file

@ -67,8 +67,6 @@ import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jakewharton.rxbinding3.view.focusChanges
import com.jakewharton.rxbinding3.widget.textChanges
import com.vanniktech.emoji.EmojiPopup import com.vanniktech.emoji.EmojiPopup
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.dialogs.ConfirmationDialogBuilder import im.vector.app.core.dialogs.ConfirmationDialogBuilder
@ -185,6 +183,10 @@ import im.vector.app.features.widgets.WidgetActivity
import im.vector.app.features.widgets.WidgetArgs import im.vector.app.features.widgets.WidgetArgs
import im.vector.app.features.widgets.WidgetKind import im.vector.app.features.widgets.WidgetKind
import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import nl.dionsegijn.konfetti.models.Shape import nl.dionsegijn.konfetti.models.Shape
@ -216,10 +218,11 @@ import org.matrix.android.sdk.api.util.MimeTypes
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
import reactivecircus.flowbinding.android.view.focusChanges
import reactivecircus.flowbinding.android.widget.textChanges
import timber.log.Timber import timber.log.Timber
import java.net.URL import java.net.URL
import java.util.UUID import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@Parcelize @Parcelize
@ -366,11 +369,11 @@ class RoomDetailFragment @Inject constructor(
} }
sharedActionViewModel sharedActionViewModel
.observe() .stream()
.subscribe { .onEach {
handleActions(it) handleActions(it)
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
knownCallsViewModel knownCallsViewModel
.liveKnownCalls .liveKnownCalls
@ -1358,19 +1361,19 @@ class RoomDetailFragment @Inject constructor(
private fun observerUserTyping() { private fun observerUserTyping() {
views.composerLayout.views.composerEditText.textChanges() views.composerLayout.views.composerEditText.textChanges()
.skipInitialValue() .skipInitialValue()
.debounce(300, TimeUnit.MILLISECONDS) .debounce(300)
.map { it.isNotEmpty() } .map { it.isNotEmpty() }
.subscribe { .onEach {
Timber.d("Typing: User is typing: $it") Timber.d("Typing: User is typing: $it")
textComposerViewModel.handle(TextComposerAction.UserIsTyping(it)) textComposerViewModel.handle(TextComposerAction.UserIsTyping(it))
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
views.composerLayout.views.composerEditText.focusChanges() views.composerLayout.views.composerEditText.focusChanges()
.subscribe { .onEach {
roomDetailViewModel.handle(RoomDetailAction.ComposerFocusChange(it)) roomDetailViewModel.handle(RoomDetailAction.ComposerFocusChange(it))
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun sendUri(uri: Uri): Boolean { private fun sendUri(uri: Uri): Boolean {

View file

@ -27,16 +27,17 @@ import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import im.vector.app.BuildConfig import im.vector.app.BuildConfig
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.flow.chunk
import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.mvrx.runCatchingToAsync
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.BehaviorDataSource
import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.attachments.toContentAttachmentData
import im.vector.app.features.call.conference.ConferenceEvent import im.vector.app.features.call.conference.ConferenceEvent
import im.vector.app.features.call.conference.JitsiActiveConferenceHolder import im.vector.app.features.call.conference.JitsiActiveConferenceHolder
@ -56,13 +57,14 @@ import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorDataStore import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.voice.VoicePlayerHelper import im.vector.app.features.voice.VoicePlayerHelper
import io.reactivex.rxkotlin.subscribeBy
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -98,7 +100,6 @@ import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.flow.unwrap
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
class RoomDetailViewModel @AssistedInject constructor( class RoomDetailViewModel @AssistedInject constructor(
@ -123,8 +124,8 @@ class RoomDetailViewModel @AssistedInject constructor(
private val room = session.getRoom(initialState.roomId)!! private val room = session.getRoom(initialState.roomId)!!
private val eventId = initialState.eventId private val eventId = initialState.eventId
private val invisibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsInvisible>() private val invisibleEventsSource = BehaviorDataSource<RoomDetailAction.TimelineEventTurnsInvisible>()
private val visibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsVisible>() private val visibleEventsSource = BehaviorDataSource<RoomDetailAction.TimelineEventTurnsVisible>()
private var timelineEvents = MutableSharedFlow<List<TimelineEvent>>(0) private var timelineEvents = MutableSharedFlow<List<TimelineEvent>>(0)
val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId) val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId)
@ -562,7 +563,7 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
private fun handleEventInvisible(action: RoomDetailAction.TimelineEventTurnsInvisible) { private fun handleEventInvisible(action: RoomDetailAction.TimelineEventTurnsInvisible) {
invisibleEventsObservable.accept(action) invisibleEventsSource.post(action)
} }
fun getMember(userId: String): RoomMemberSummary? { fun getMember(userId: String): RoomMemberSummary? {
@ -711,12 +712,12 @@ class RoomDetailViewModel @AssistedInject constructor(
private fun handleEventVisible(action: RoomDetailAction.TimelineEventTurnsVisible) { private fun handleEventVisible(action: RoomDetailAction.TimelineEventTurnsVisible) {
viewModelScope.launch(Dispatchers.Default) { viewModelScope.launch(Dispatchers.Default) {
if (action.event.root.sendState.isSent()) { // ignore pending/local events if (action.event.root.sendState.isSent()) { // ignore pending/local events
visibleEventsObservable.accept(action) visibleEventsSource.post(action)
} }
// We need to update this with the related m.replace also (to move read receipt) // We need to update this with the related m.replace also (to move read receipt)
action.event.annotations?.editSummary?.sourceEvents?.forEach { action.event.annotations?.editSummary?.sourceEvents?.forEach {
room.getTimeLineEvent(it)?.let { event -> room.getTimeLineEvent(it)?.let { event ->
visibleEventsObservable.accept(RoomDetailAction.TimelineEventTurnsVisible(event)) visibleEventsSource.post(RoomDetailAction.TimelineEventTurnsVisible(event))
} }
} }
@ -864,11 +865,13 @@ class RoomDetailViewModel @AssistedInject constructor(
private fun observeEventDisplayedActions() { private fun observeEventDisplayedActions() {
// We are buffering scroll events for one second // We are buffering scroll events for one second
// and keep the most recent one to set the read receipt on. // and keep the most recent one to set the read receipt on.
visibleEventsObservable
.buffer(1, TimeUnit.SECONDS) visibleEventsSource
.stream()
.chunk(1000)
.filter { it.isNotEmpty() } .filter { it.isNotEmpty() }
.subscribeBy(onNext = { actions -> .onEach { actions ->
val bufferedMostRecentDisplayedEvent = actions.maxByOrNull { it.event.displayIndex }?.event ?: return@subscribeBy val bufferedMostRecentDisplayedEvent = actions.maxByOrNull { it.event.displayIndex }?.event ?: return@onEach
val globalMostRecentDisplayedEvent = mostRecentDisplayedEvent val globalMostRecentDisplayedEvent = mostRecentDisplayedEvent
if (trackUnreadMessages.get()) { if (trackUnreadMessages.get()) {
if (globalMostRecentDisplayedEvent == null) { if (globalMostRecentDisplayedEvent == null) {
@ -882,8 +885,9 @@ class RoomDetailViewModel @AssistedInject constructor(
tryOrNull { room.setReadReceipt(eventId) } tryOrNull { room.setReadReceipt(eventId) }
} }
} }
}) }
.disposeOnClear() .flowOn(Dispatchers.Default)
.launchIn(viewModelScope)
} }
private fun handleMarkAllAsRead() { private fun handleMarkAllAsRead() {

View file

@ -104,7 +104,7 @@ class TextComposerViewModel @AssistedInject constructor(
} }
private fun subscribeToStateInternal() { private fun subscribeToStateInternal() {
selectSubscribe(TextComposerViewState::sendMode, TextComposerViewState::canSendMessage, TextComposerViewState::isVoiceRecording) { _, _, _ -> onEach(TextComposerViewState::sendMode, TextComposerViewState::canSendMessage, TextComposerViewState::isVoiceRecording) { _, _, _ ->
updateIsSendButtonVisibility(false) updateIsSendButtonVisibility(false)
} }
} }

View file

@ -31,18 +31,16 @@ import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
import io.reactivex.Observable import kotlinx.coroutines.flow.map
import io.reactivex.Single
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.rx.RxRoom import org.matrix.android.sdk.flow.unwrap
import org.matrix.android.sdk.rx.unwrap
data class DisplayReactionsViewState( data class DisplayReactionsViewState(
val eventId: String, val eventId: String,
val roomId: String, val roomId: String,
val mapReactionKeyToMemberList: Async<List<ReactionInfo>> = Uninitialized) : val mapReactionKeyToMemberList: Async<List<ReactionInfo>> = Uninitialized) :
MavericksState { MavericksState {
constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId) constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId)
} }
@ -81,39 +79,31 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted
} }
private fun observeEventAnnotationSummaries() { private fun observeEventAnnotationSummaries() {
RxRoom(room) room.flow()
.liveAnnotationSummary(eventId) .liveAnnotationSummary(eventId)
.unwrap() .unwrap()
.flatMapSingle { summaries -> .map { annotationsSummary ->
Observable annotationsSummary.reactionsSummary
.fromIterable(summaries.reactionsSummary) .flatMap { reactionsSummary ->
// .filter { reactionAggregatedSummary -> isSingleEmoji(reactionAggregatedSummary.key) } reactionsSummary.sourceEvents.map {
.toReactionInfoList() val event = room.getTimeLineEvent(it)
?: throw RuntimeException("Your eventId is not valid")
ReactionInfo(
event.root.eventId!!,
reactionsSummary.key,
event.root.senderId ?: "",
event.senderInfo.disambiguatedDisplayName,
dateFormatter.format(event.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
)
}
}
} }
.execute { .execute {
copy(mapReactionKeyToMemberList = it) copy(mapReactionKeyToMemberList = it)
} }
} }
private fun Observable<ReactionAggregatedSummary>.toReactionInfoList(): Single<List<ReactionInfo>> {
return flatMap { summary ->
Observable
.fromIterable(summary.sourceEvents)
.map {
val event = room.getTimeLineEvent(it)
?: throw RuntimeException("Your eventId is not valid")
ReactionInfo(
event.root.eventId!!,
summary.key,
event.root.senderId ?: "",
event.senderInfo.disambiguatedDisplayName,
dateFormatter.format(event.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
)
}
}.toList()
}
override fun handle(action: EmptyAction) { override fun handle(action: EmptyAction) {
// No op // No op
} }

View file

@ -16,8 +16,8 @@
package im.vector.app.features.home.room.list package im.vector.app.features.home.room.list
import androidx.core.util.Predicate
import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.RoomListDisplayMode
import io.reactivex.functions.Predicate
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary

View file

@ -23,6 +23,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -49,6 +50,8 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.home.room.list.widget.NotifsFabMenuView import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.notifications.NotificationDrawerManager
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.extensions.orTrue
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
@ -118,9 +121,9 @@ class RoomListFragment @Inject constructor(
views.createChatFabMenu.listener = this views.createChatFabMenu.listener = this
sharedActionViewModel sharedActionViewModel
.observe() .stream()
.subscribe { handleQuickActions(it) } .onEach { handleQuickActions(it) }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
roomListViewModel.onEach(RoomListViewState::roomMembershipChanges) { ms -> roomListViewModel.onEach(RoomListViewState::roomMembershipChanges) { ms ->
// it's for invites local echo // it's for invites local echo

View file

@ -16,7 +16,7 @@
package im.vector.app.features.home.room.list package im.vector.app.features.home.room.list
import io.reactivex.functions.Predicate import androidx.core.util.Predicate
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject import javax.inject.Inject

View file

@ -20,6 +20,4 @@ import im.vector.app.features.home.RoomListDisplayMode
interface RoomListSectionBuilder { interface RoomListSectionBuilder {
fun buildSections(mode: RoomListDisplayMode): List<RoomsSection> fun buildSections(mode: RoomListDisplayMode): List<RoomsSection>
fun dispose()
} }

View file

@ -17,6 +17,7 @@
package im.vector.app.features.home.room.list package im.vector.app.features.home.room.list
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.lifecycle.asFlow
import im.vector.app.AppStateHandler import im.vector.app.AppStateHandler
import im.vector.app.R import im.vector.app.R
import im.vector.app.RoomGroupingMethod import im.vector.app.RoomGroupingMethod
@ -24,17 +25,21 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.showInvites import im.vector.app.features.invite.showInvites
import io.reactivex.disposables.CompositeDisposable import kotlinx.coroutines.CoroutineScope
import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.query.RoomTagQueryFilter
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.rx.asObservable
class RoomListSectionBuilderGroup( class RoomListSectionBuilderGroup(
private val coroutineScope: CoroutineScope,
private val session: Session, private val session: Session,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val appStateHandler: AppStateHandler, private val appStateHandler: AppStateHandler,
@ -42,8 +47,6 @@ class RoomListSectionBuilderGroup(
private val onUpdatable: (UpdatableLivePageResult) -> Unit private val onUpdatable: (UpdatableLivePageResult) -> Unit
) : RoomListSectionBuilder { ) : RoomListSectionBuilder {
private val disposables = CompositeDisposable()
override fun buildSections(mode: RoomListDisplayMode): List<RoomsSection> { override fun buildSections(mode: RoomListDisplayMode): List<RoomsSection> {
val activeGroupAwareQueries = mutableListOf<UpdatableLivePageResult>() val activeGroupAwareQueries = mutableListOf<UpdatableLivePageResult>()
val sections = mutableListOf<RoomsSection>() val sections = mutableListOf<RoomsSection>()
@ -103,16 +106,14 @@ class RoomListSectionBuilderGroup(
appStateHandler.selectedRoomGroupingObservable appStateHandler.selectedRoomGroupingObservable
.distinctUntilChanged() .distinctUntilChanged()
.subscribe { groupingMethod -> .onEach { groupingMethod ->
val selectedGroupId = (groupingMethod.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId val selectedGroupId = (groupingMethod.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId
activeGroupAwareQueries.onEach { updater -> activeGroupAwareQueries.onEach { updater ->
updater.updateQuery { query -> updater.updateQuery { query ->
query.copy(activeGroupId = selectedGroupId) query.copy(activeGroupId = selectedGroupId)
} }
} }
}.also { }.launchIn(coroutineScope)
disposables.add(it)
}
return sections return sections
} }
@ -251,15 +252,14 @@ class RoomListSectionBuilderGroup(
}.livePagedList }.livePagedList
.let { livePagedList -> .let { livePagedList ->
// use it also as a source to update count // use it also as a source to update count
livePagedList.asObservable() livePagedList.asFlow()
.observeOn(Schedulers.computation()) .onEach {
.subscribe {
sections.find { it.sectionName == name } sections.find { it.sectionName == name }
?.notificationCount ?.notificationCount
?.postValue(session.getNotificationCountForRooms(roomQueryParams)) ?.postValue(session.getNotificationCountForRooms(roomQueryParams))
}.also {
disposables.add(it)
} }
.flowOn(Dispatchers.Default)
.launchIn(coroutineScope)
sections.add( sections.add(
RoomsSection( RoomsSection(
@ -280,8 +280,4 @@ class RoomListSectionBuilderGroup(
.build() .build()
.let { block(it) } .let { block(it) }
} }
override fun dispose() {
disposables.dispose()
}
} }

View file

@ -19,6 +19,7 @@ package im.vector.app.features.home.room.list
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asFlow
import androidx.lifecycle.liveData import androidx.lifecycle.liveData
import androidx.paging.PagedList import androidx.paging.PagedList
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
@ -29,12 +30,15 @@ import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.showInvites import im.vector.app.features.invite.showInvites
import im.vector.app.space import im.vector.app.space
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.Observables
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter
@ -44,7 +48,7 @@ import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
import org.matrix.android.sdk.rx.asObservable import timber.log.Timber
class RoomListSectionBuilderSpace( class RoomListSectionBuilderSpace(
private val session: Session, private val session: Session,
@ -57,8 +61,6 @@ class RoomListSectionBuilderSpace(
private val onlyOrphansInHome: Boolean = false private val onlyOrphansInHome: Boolean = false
) : RoomListSectionBuilder { ) : RoomListSectionBuilder {
private val disposables = CompositeDisposable()
private val pagedListConfig = PagedList.Config.Builder() private val pagedListConfig = PagedList.Config.Builder()
.setPageSize(10) .setPageSize(10)
.setInitialLoadSizeHint(20) .setInitialLoadSizeHint(20)
@ -132,14 +134,12 @@ class RoomListSectionBuilderSpace(
appStateHandler.selectedRoomGroupingObservable appStateHandler.selectedRoomGroupingObservable
.distinctUntilChanged() .distinctUntilChanged()
.subscribe { groupingMethod -> .onEach { groupingMethod ->
val selectedSpace = groupingMethod.orNull()?.space() val selectedSpace = groupingMethod.orNull()?.space()
activeSpaceAwareQueries.onEach { updater -> activeSpaceAwareQueries.onEach { updater ->
updater.updateForSpaceId(selectedSpace?.roomId) updater.updateForSpaceId(selectedSpace?.roomId)
} }
}.also { }.launchIn(viewModelScope)
disposables.add(it)
}
return sections return sections
} }
@ -221,13 +221,13 @@ class RoomListSectionBuilderSpace(
} }
// add suggested rooms // add suggested rooms
val suggestedRoomsObservable = // MutableLiveData<List<SpaceChildInfo>>() val suggestedRoomsFlow = // MutableLiveData<List<SpaceChildInfo>>()
appStateHandler.selectedRoomGroupingObservable appStateHandler.selectedRoomGroupingObservable
.distinctUntilChanged() .distinctUntilChanged()
.switchMap { groupingMethod -> .flatMapLatest { groupingMethod ->
val selectedSpace = groupingMethod.orNull()?.space() val selectedSpace = groupingMethod.orNull()?.space()
if (selectedSpace == null) { if (selectedSpace == null) {
Observable.just(emptyList()) flowOf(emptyList())
} else { } else {
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) { liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
val spaceSum = tryOrNull { val spaceSum = tryOrNull {
@ -240,24 +240,23 @@ class RoomListSectionBuilderSpace(
session.getRoomSummary(it.childRoomId)?.membership?.isActive() != true session.getRoomSummary(it.childRoomId)?.membership?.isActive() != true
} }
emit(filtered) emit(filtered)
}.asObservable() }.asFlow()
} }
} }
val liveSuggestedRooms = MutableLiveData<SuggestedRoomInfo>() val liveSuggestedRooms = MutableLiveData<SuggestedRoomInfo>()
Observables.combineLatest( combine(
suggestedRoomsObservable, suggestedRoomsFlow,
suggestedRoomJoiningState.asObservable() suggestedRoomJoiningState.asFlow()
) { rooms, joinStates -> ) { rooms, joinStates ->
SuggestedRoomInfo( SuggestedRoomInfo(
rooms, rooms,
joinStates joinStates
) )
}.subscribe { }.onEach {
liveSuggestedRooms.postValue(it) liveSuggestedRooms.postValue(it)
}.also { }.launchIn(viewModelScope)
disposables.add(it)
}
sections.add( sections.add(
RoomsSection( RoomsSection(
sectionName = stringProvider.getString(R.string.suggested_header), sectionName = stringProvider.getString(R.string.suggested_header),
@ -373,9 +372,9 @@ class RoomListSectionBuilderSpace(
}.livePagedList }.livePagedList
.let { livePagedList -> .let { livePagedList ->
// use it also as a source to update count // use it also as a source to update count
livePagedList.asObservable() livePagedList.asFlow()
.observeOn(Schedulers.computation()) .onEach {
.subscribe { Timber.v("Thread space list: ${Thread.currentThread()}")
sections.find { it.sectionName == name } sections.find { it.sectionName == name }
?.notificationCount ?.notificationCount
?.postValue( ?.postValue(
@ -387,9 +386,9 @@ class RoomListSectionBuilderSpace(
) )
} }
) )
}.also {
disposables.add(it)
} }
.flowOn(Dispatchers.Default)
.launchIn(viewModelScope)
sections.add( sections.add(
RoomsSection( RoomsSection(
@ -432,8 +431,4 @@ class RoomListSectionBuilderSpace(
RoomListViewModel.SpaceFilterStrategy.NONE -> this RoomListViewModel.SpaceFilterStrategy.NONE -> this
} }
} }
override fun dispose() {
disposables.dispose()
}
} }

View file

@ -135,6 +135,7 @@ class RoomListViewModel @AssistedInject constructor(
) )
} else { } else {
RoomListSectionBuilderGroup( RoomListSectionBuilderGroup(
viewModelScope,
session, session,
stringProvider, stringProvider,
appStateHandler, appStateHandler,
@ -335,9 +336,4 @@ class RoomListViewModel @AssistedInject constructor(
_viewEvents.post(value) _viewEvents.post(value)
} }
} }
override fun onCleared() {
super.onCleared()
roomListSectionBuilder.dispose()
}
} }

View file

@ -21,6 +21,7 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.View import android.view.View
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.viewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -41,6 +42,8 @@ import im.vector.app.features.userdirectory.UserListFragment
import im.vector.app.features.userdirectory.UserListFragmentArgs import im.vector.app.features.userdirectory.UserListFragmentArgs
import im.vector.app.features.userdirectory.UserListSharedAction import im.vector.app.features.userdirectory.UserListSharedAction
import im.vector.app.features.userdirectory.UserListSharedActionViewModel import im.vector.app.features.userdirectory.UserListSharedActionViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import java.net.HttpURLConnection import java.net.HttpURLConnection
@ -63,8 +66,8 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
sharedActionViewModel sharedActionViewModel
.observe() .stream()
.subscribe { sharedAction -> .onEach { sharedAction ->
when (sharedAction) { when (sharedAction) {
UserListSharedAction.Close -> finish() UserListSharedAction.Close -> finish()
UserListSharedAction.GoBack -> onBackPressed() UserListSharedAction.GoBack -> onBackPressed()
@ -75,7 +78,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
} }
} }
} }
.disposeOnDestroy() .launchIn(lifecycleScope)
if (isFirstCreation()) { if (isFirstCreation()) {
addFragment( addFragment(
R.id.container, R.id.container,

View file

@ -18,11 +18,14 @@ package im.vector.app.features.invite
import im.vector.app.ActiveSessionDataSource import im.vector.app.ActiveSessionDataSource
import im.vector.app.features.session.coroutineScope import im.vector.app.features.session.coroutineScope
import io.reactivex.disposables.Disposable import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -51,7 +54,8 @@ class InvitesAcceptor @Inject constructor(
private val autoAcceptInvites: AutoAcceptInvites private val autoAcceptInvites: AutoAcceptInvites
) : Session.Listener { ) : Session.Listener {
private lateinit var activeSessionDisposable: Disposable private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val shouldRejectRoomIds = mutableSetOf<String>() private val shouldRejectRoomIds = mutableSetOf<String>()
private val activeSessionIds = mutableSetOf<String>() private val activeSessionIds = mutableSetOf<String>()
private val semaphore = Semaphore(1) private val semaphore = Semaphore(1)
@ -61,13 +65,14 @@ class InvitesAcceptor @Inject constructor(
} }
private fun observeActiveSession() { private fun observeActiveSession() {
activeSessionDisposable = sessionDataSource.observe() sessionDataSource.stream()
.distinctUntilChanged() .distinctUntilChanged()
.subscribe { .onEach {
it.orNull()?.let { session -> it.orNull()?.let { session ->
onSessionActive(session) onSessionActive(session)
} }
} }
.launchIn(coroutineScope)
} }
private fun onSessionActive(session: Session) { private fun onSessionActive(session: Session) {

View file

@ -85,10 +85,9 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), ToolbarCo
addFirstFragment() addFirstFragment()
} }
loginViewModel loginViewModel.onEach {
.subscribe(this) { updateWithState(it)
updateWithState(it) }
}
loginViewModel.observeViewEvents { handleLoginViewEvents(it) } loginViewModel.observeViewEvents { handleLoginViewEvents(it) }

View file

@ -25,21 +25,24 @@ import android.view.inputmethod.EditorInfo
import androidx.autofill.HintConstants import androidx.autofill.HintConstants
import androidx.core.text.isDigitsOnly import androidx.core.text.isDigitsOnly
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.hidePassword import im.vector.app.core.extensions.hidePassword
import im.vector.app.core.extensions.toReducedUrl import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentLoginBinding import im.vector.app.databinding.FragmentLoginBinding
import io.reactivex.Observable import kotlinx.coroutines.flow.combine
import io.reactivex.rxkotlin.subscribeBy import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidPassword
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -224,20 +227,18 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
private fun setupSubmitButton() { private fun setupSubmitButton() {
views.loginSubmit.setOnClickListener { submit() } views.loginSubmit.setOnClickListener { submit() }
Observable combine(
.combineLatest( views.loginField.textChanges().map { it.trim().isNotEmpty() },
views.loginField.textChanges().map { it.trim().isNotEmpty() }, views.passwordField.textChanges().map { it.isNotEmpty() }
views.passwordField.textChanges().map { it.isNotEmpty() }, ) { isLoginNotEmpty, isPasswordNotEmpty ->
{ isLoginNotEmpty, isPasswordNotEmpty -> isLoginNotEmpty && isPasswordNotEmpty
isLoginNotEmpty && isPasswordNotEmpty }
} .onEach {
)
.subscribeBy {
views.loginFieldTil.error = null views.loginFieldTil.error = null
views.passwordFieldTil.error = null views.passwordFieldTil.error = null
views.loginSubmit.isEnabled = it views.loginSubmit.isEnabled = it
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun forgetPasswordClicked() { private fun forgetPasswordClicked() {

View file

@ -25,19 +25,22 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.autofill.HintConstants import androidx.autofill.HintConstants
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.NumberParseException
import com.google.i18n.phonenumbers.PhoneNumberUtil import com.google.i18n.phonenumbers.PhoneNumberUtil
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.isEmail
import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.databinding.FragmentLoginGenericTextInputFormBinding import im.vector.app.databinding.FragmentLoginGenericTextInputFormBinding
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.is401 import org.matrix.android.sdk.api.failure.is401
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
enum class TextInputFormFragmentMode { enum class TextInputFormFragmentMode {
@ -93,10 +96,10 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
private fun setupTil() { private fun setupTil() {
views.loginGenericTextInputFormTextInput.textChanges() views.loginGenericTextInputFormTextInput.textChanges()
.subscribe { .onEach {
views.loginGenericTextInputFormTil.error = null views.loginGenericTextInputFormTil.error = null
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun setupUi() { private fun setupUi() {
@ -195,10 +198,10 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
private fun setupSubmitButton() { private fun setupSubmitButton() {
views.loginGenericTextInputFormSubmit.isEnabled = false views.loginGenericTextInputFormSubmit.isEnabled = false
views.loginGenericTextInputFormTextInput.textChanges() views.loginGenericTextInputFormTextInput.textChanges()
.subscribe { .onEach {
views.loginGenericTextInputFormSubmit.isEnabled = isInputValid(it) views.loginGenericTextInputFormSubmit.isEnabled = isInputValid(it)
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun isInputValid(input: CharSequence): Boolean { private fun isInputValid(input: CharSequence): Boolean {

View file

@ -20,19 +20,22 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.hidePassword import im.vector.app.core.extensions.hidePassword
import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.isEmail
import im.vector.app.core.extensions.toReducedUrl import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentLoginResetPasswordBinding import im.vector.app.databinding.FragmentLoginResetPasswordBinding
import io.reactivex.Observable import kotlinx.coroutines.flow.combine
import io.reactivex.rxkotlin.subscribeBy import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -59,21 +62,18 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment<F
private fun setupSubmitButton() { private fun setupSubmitButton() {
views.resetPasswordSubmit.setOnClickListener { submit() } views.resetPasswordSubmit.setOnClickListener { submit() }
combine(
Observable views.resetPasswordEmail.textChanges().map { it.isEmail() },
.combineLatest( views.passwordField.textChanges().map { it.isNotEmpty() }
views.resetPasswordEmail.textChanges().map { it.isEmail() }, ) { isEmail, isPasswordNotEmpty ->
views.passwordField.textChanges().map { it.isNotEmpty() }, isEmail && isPasswordNotEmpty
{ isEmail, isPasswordNotEmpty -> }
isEmail && isPasswordNotEmpty .onEach {
}
)
.subscribeBy {
views.resetPasswordEmailTil.error = null views.resetPasswordEmailTil.error = null
views.passwordFieldTil.error = null views.passwordFieldTil.error = null
views.resetPasswordSubmit.isEnabled = it views.resetPasswordSubmit.isEnabled = it
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun submit() { private fun submit() {

View file

@ -25,15 +25,18 @@ import android.view.inputmethod.EditorInfo
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.BuildConfig import im.vector.app.BuildConfig
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.utils.ensureProtocol import im.vector.app.core.utils.ensureProtocol
import im.vector.app.core.utils.openUrlInChromeCustomTab import im.vector.app.core.utils.openUrlInChromeCustomTab
import im.vector.app.databinding.FragmentLoginServerUrlFormBinding import im.vector.app.databinding.FragmentLoginServerUrlFormBinding
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import reactivecircus.flowbinding.android.widget.textChanges
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
@ -61,11 +64,11 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment<F
private fun setupHomeServerField() { private fun setupHomeServerField() {
views.loginServerUrlFormHomeServerUrl.textChanges() views.loginServerUrlFormHomeServerUrl.textChanges()
.subscribe { .onEach {
views.loginServerUrlFormHomeServerUrlTil.error = null views.loginServerUrlFormHomeServerUrlTil.error = null
views.loginServerUrlFormSubmit.isEnabled = it.isNotBlank() views.loginServerUrlFormSubmit.isEnabled = it.isNotBlank()
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
views.loginServerUrlFormHomeServerUrl.setOnEditorActionListener { _, actionId, _ -> views.loginServerUrlFormHomeServerUrl.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) { if (actionId == EditorInfo.IME_ACTION_DONE) {

View file

@ -92,10 +92,9 @@ open class LoginActivity2 : VectorBaseActivity<ActivityLoginBinding>(), ToolbarC
addFirstFragment() addFirstFragment()
} }
loginViewModel loginViewModel.onEach {
.subscribe(this) { updateWithState(it)
updateWithState(it) }
}
loginViewModel.observeViewEvents { handleLoginViewEvents(it) } loginViewModel.observeViewEvents { handleLoginViewEvents(it) }
@ -201,19 +200,19 @@ open class LoginActivity2 : VectorBaseActivity<ActivityLoginBinding>(), ToolbarC
// Go back to the login fragment // Go back to the login fragment
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
} }
is LoginViewEvents2.OnSendEmailSuccess -> is LoginViewEvents2.OnSendEmailSuccess ->
addFragmentToBackstack(R.id.loginFragmentContainer, addFragmentToBackstack(R.id.loginFragmentContainer,
LoginWaitForEmailFragment2::class.java, LoginWaitForEmailFragment2::class.java,
LoginWaitForEmailFragmentArgument(event.email), LoginWaitForEmailFragmentArgument(event.email),
tag = FRAGMENT_REGISTRATION_STAGE_TAG, tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption) option = commonOption)
is LoginViewEvents2.OpenSigninPasswordScreen -> { is LoginViewEvents2.OpenSigninPasswordScreen -> {
addFragmentToBackstack(R.id.loginFragmentContainer, addFragmentToBackstack(R.id.loginFragmentContainer,
LoginFragmentSigninPassword2::class.java, LoginFragmentSigninPassword2::class.java,
tag = FRAGMENT_LOGIN_TAG, tag = FRAGMENT_LOGIN_TAG,
option = commonOption) option = commonOption)
} }
is LoginViewEvents2.OpenSignupPasswordScreen -> { is LoginViewEvents2.OpenSignupPasswordScreen -> {
addFragmentToBackstack(R.id.loginFragmentContainer, addFragmentToBackstack(R.id.loginFragmentContainer,
LoginFragmentSignupPassword2::class.java, LoginFragmentSignupPassword2::class.java,
tag = FRAGMENT_REGISTRATION_STAGE_TAG, tag = FRAGMENT_REGISTRATION_STAGE_TAG,

View file

@ -24,17 +24,20 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.autofill.HintConstants import androidx.autofill.HintConstants
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.hidePassword import im.vector.app.core.extensions.hidePassword
import im.vector.app.databinding.FragmentLoginSigninPassword2Binding import im.vector.app.databinding.FragmentLoginSigninPassword2Binding
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import io.reactivex.rxkotlin.subscribeBy import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.auth.login.LoginProfileInfo import org.matrix.android.sdk.api.auth.login.LoginProfileInfo
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidPassword
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
@ -121,11 +124,11 @@ class LoginFragmentSigninPassword2 @Inject constructor(
views.passwordField views.passwordField
.textChanges() .textChanges()
.map { it.isNotEmpty() } .map { it.isNotEmpty() }
.subscribeBy { .onEach {
views.passwordFieldTil.error = null views.passwordFieldTil.error = null
views.loginSubmit.isEnabled = it views.loginSubmit.isEnabled = it
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun forgetPasswordClicked() { private fun forgetPasswordClicked() {

View file

@ -22,13 +22,16 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.autofill.HintConstants import androidx.autofill.HintConstants
import com.jakewharton.rxbinding3.widget.textChanges import androidx.lifecycle.lifecycleScope
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.databinding.FragmentLoginSigninUsername2Binding import im.vector.app.databinding.FragmentLoginSigninUsername2Binding
import io.reactivex.rxkotlin.subscribeBy import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixError
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -83,11 +86,11 @@ class LoginFragmentSigninUsername2 @Inject constructor() : AbstractLoginFragment
views.loginSubmit.setOnClickListener { submit() } views.loginSubmit.setOnClickListener { submit() }
views.loginField.textChanges() views.loginField.textChanges()
.map { it.trim().isNotEmpty() } .map { it.trim().isNotEmpty() }
.subscribeBy { .onEach {
views.loginFieldTil.error = null views.loginFieldTil.error = null
views.loginSubmit.isEnabled = it views.loginSubmit.isEnabled = it
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
override fun resetViewModel() { override fun resetViewModel() {

View file

@ -23,12 +23,14 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.autofill.HintConstants import androidx.autofill.HintConstants
import com.jakewharton.rxbinding3.widget.textChanges import androidx.lifecycle.lifecycleScope
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.hidePassword import im.vector.app.core.extensions.hidePassword
import im.vector.app.databinding.FragmentLoginSignupPassword2Binding import im.vector.app.databinding.FragmentLoginSignupPassword2Binding
import io.reactivex.rxkotlin.subscribeBy import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -87,11 +89,11 @@ class LoginFragmentSignupPassword2 @Inject constructor() : AbstractLoginFragment
private fun setupSubmitButton() { private fun setupSubmitButton() {
views.loginSubmit.setOnClickListener { submit() } views.loginSubmit.setOnClickListener { submit() }
views.passwordField.textChanges() views.passwordField.textChanges()
.subscribeBy { password -> .onEach { password ->
views.passwordFieldTil.error = null views.passwordFieldTil.error = null
views.loginSubmit.isEnabled = password.isNotEmpty() views.loginSubmit.isEnabled = password.isNotEmpty()
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
override fun resetViewModel() { override fun resetViewModel() {

View file

@ -24,14 +24,17 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.autofill.HintConstants import androidx.autofill.HintConstants
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.jakewharton.rxbinding3.widget.textChanges import androidx.lifecycle.lifecycleScope
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.toReducedUrl import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentLoginSignupUsername2Binding import im.vector.app.databinding.FragmentLoginSignupUsername2Binding
import im.vector.app.features.login.LoginMode import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.SocialLoginButtonsView import im.vector.app.features.login.SocialLoginButtonsView
import io.reactivex.rxkotlin.subscribeBy import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -111,12 +114,12 @@ class LoginFragmentSignupUsername2 @Inject constructor() : AbstractSSOLoginFragm
views.loginSubmit.setOnClickListener { submit() } views.loginSubmit.setOnClickListener { submit() }
views.loginField.textChanges() views.loginField.textChanges()
.map { it.trim() } .map { it.trim() }
.subscribeBy { text -> .onEach { text ->
val isNotEmpty = text.isNotEmpty() val isNotEmpty = text.isNotEmpty()
views.loginFieldTil.error = null views.loginFieldTil.error = null
views.loginSubmit.isEnabled = isNotEmpty views.loginSubmit.isEnabled = isNotEmpty
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
override fun resetViewModel() { override fun resetViewModel() {

View file

@ -24,7 +24,7 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.autofill.HintConstants import androidx.autofill.HintConstants
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.jakewharton.rxbinding3.widget.textChanges import androidx.lifecycle.lifecycleScope
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.hidePassword import im.vector.app.core.extensions.hidePassword
@ -32,11 +32,14 @@ import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentLoginSigninToAny2Binding import im.vector.app.databinding.FragmentLoginSigninToAny2Binding
import im.vector.app.features.login.LoginMode import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.SocialLoginButtonsView import im.vector.app.features.login.SocialLoginButtonsView
import io.reactivex.Observable import kotlinx.coroutines.flow.combine
import io.reactivex.rxkotlin.subscribeBy import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidPassword
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -136,20 +139,18 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2<Frag
private fun setupSubmitButton() { private fun setupSubmitButton() {
views.loginSubmit.setOnClickListener { submit() } views.loginSubmit.setOnClickListener { submit() }
Observable combine(
.combineLatest( views.loginField.textChanges().map { it.trim().isNotEmpty() },
views.loginField.textChanges().map { it.trim().isNotEmpty() }, views.passwordField.textChanges().map { it.isNotEmpty() }
views.passwordField.textChanges().map { it.isNotEmpty() }, ) { isLoginNotEmpty, isPasswordNotEmpty ->
{ isLoginNotEmpty, isPasswordNotEmpty -> isLoginNotEmpty && isPasswordNotEmpty
isLoginNotEmpty && isPasswordNotEmpty }
} .onEach {
)
.subscribeBy {
views.loginFieldTil.error = null views.loginFieldTil.error = null
views.passwordFieldTil.error = null views.passwordFieldTil.error = null
views.loginSubmit.isEnabled = it views.loginSubmit.isEnabled = it
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun forgetPasswordClicked() { private fun forgetPasswordClicked() {

View file

@ -24,10 +24,10 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.autofill.HintConstants import androidx.autofill.HintConstants
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.NumberParseException
import com.google.i18n.phonenumbers.PhoneNumberUtil import com.google.i18n.phonenumbers.PhoneNumberUtil
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.isEmail
@ -36,9 +36,12 @@ import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentLoginGenericTextInputForm2Binding import im.vector.app.databinding.FragmentLoginGenericTextInputForm2Binding
import im.vector.app.features.login.LoginGenericTextInputFormFragmentArgument import im.vector.app.features.login.LoginGenericTextInputFormFragmentArgument
import im.vector.app.features.login.TextInputFormFragmentMode import im.vector.app.features.login.TextInputFormFragmentMode
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.is401 import org.matrix.android.sdk.api.failure.is401
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -82,10 +85,10 @@ class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFr
private fun setupTil() { private fun setupTil() {
views.loginGenericTextInputFormTextInput.textChanges() views.loginGenericTextInputFormTextInput.textChanges()
.subscribe { .onEach {
views.loginGenericTextInputFormTil.error = null views.loginGenericTextInputFormTil.error = null
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun setupUi() { private fun setupUi() {
@ -189,11 +192,11 @@ class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFr
private fun setupSubmitButton() { private fun setupSubmitButton() {
views.loginGenericTextInputFormSubmit.isEnabled = false views.loginGenericTextInputFormSubmit.isEnabled = false
views.loginGenericTextInputFormTextInput.textChanges() views.loginGenericTextInputFormTextInput.textChanges()
.subscribe { text -> .onEach { text ->
views.loginGenericTextInputFormSubmit.isEnabled = isInputValid(text) views.loginGenericTextInputFormSubmit.isEnabled = isInputValid(text)
text?.let { updateSubmitButtons(it) } updateSubmitButtons(text)
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun updateSubmitButtons(text: CharSequence) { private fun updateSubmitButtons(text: CharSequence) {

View file

@ -23,8 +23,8 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.autofill.HintConstants import androidx.autofill.HintConstants
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.hidePassword import im.vector.app.core.extensions.hidePassword
@ -32,8 +32,11 @@ import im.vector.app.core.extensions.isEmail
import im.vector.app.core.extensions.toReducedUrl import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.core.utils.autoResetTextInputLayoutErrors import im.vector.app.core.utils.autoResetTextInputLayoutErrors
import im.vector.app.databinding.FragmentLoginResetPassword2Binding import im.vector.app.databinding.FragmentLoginResetPassword2Binding
import io.reactivex.Observable import kotlinx.coroutines.flow.combine
import io.reactivex.rxkotlin.subscribeBy import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -78,19 +81,16 @@ class LoginResetPasswordFragment2 @Inject constructor() : AbstractLoginFragment2
private fun setupSubmitButton() { private fun setupSubmitButton() {
views.resetPasswordSubmit.setOnClickListener { submit() } views.resetPasswordSubmit.setOnClickListener { submit() }
combine(
Observable views.resetPasswordEmail.textChanges().map { it.isEmail() },
.combineLatest( views.passwordField.textChanges().map { it.isNotEmpty() }
views.resetPasswordEmail.textChanges().map { it.isEmail() }, ) { isEmail, isPasswordNotEmpty ->
views.passwordField.textChanges().map { it.isNotEmpty() }, isEmail && isPasswordNotEmpty
{ isEmail, isPasswordNotEmpty -> }
isEmail && isPasswordNotEmpty .onEach {
}
)
.subscribeBy {
views.resetPasswordSubmit.isEnabled = it views.resetPasswordSubmit.isEnabled = it
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun submit() { private fun submit() {

View file

@ -24,15 +24,18 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.lifecycle.lifecycleScope
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.BuildConfig import im.vector.app.BuildConfig
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.utils.ensureProtocol import im.vector.app.core.utils.ensureProtocol
import im.vector.app.databinding.FragmentLoginServerUrlForm2Binding import im.vector.app.databinding.FragmentLoginServerUrlForm2Binding
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixError
import reactivecircus.flowbinding.android.widget.textChanges
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
@ -60,11 +63,11 @@ class LoginServerUrlFormFragment2 @Inject constructor() : AbstractLoginFragment2
private fun setupHomeServerField() { private fun setupHomeServerField() {
views.loginServerUrlFormHomeServerUrl.textChanges() views.loginServerUrlFormHomeServerUrl.textChanges()
.subscribe { .onEach {
views.loginServerUrlFormHomeServerUrlTil.error = null views.loginServerUrlFormHomeServerUrlTil.error = null
views.loginServerUrlFormSubmit.isEnabled = it.isNotBlank() views.loginServerUrlFormSubmit.isEnabled = it.isNotBlank()
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
views.loginServerUrlFormHomeServerUrl.setOnEditorActionListener { _, actionId, _ -> views.loginServerUrlFormHomeServerUrl.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) { if (actionId == EditorInfo.IME_ACTION_DONE) {

View file

@ -33,8 +33,8 @@ class PowerLevelsFlowFactory(private val room: Room) {
fun createFlow(): Flow<PowerLevelsContent> { fun createFlow(): Flow<PowerLevelsContent> {
return room.flow() return room.flow()
.liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
.flowOn(Dispatchers.Default)
.mapOptional { it.content.toModel<PowerLevelsContent>() } .mapOptional { it.content.toModel<PowerLevelsContent>() }
.flowOn(Dispatchers.Default)
.unwrap() .unwrap()
} }
} }

View file

@ -28,18 +28,18 @@ import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.viewModel
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.jakewharton.rxbinding3.widget.queryTextChanges
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.EmojiCompatFontProvider import im.vector.app.EmojiCompatFontProvider
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.observeEvent import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityEmojiReactionPickerBinding import im.vector.app.databinding.ActivityEmojiReactionPickerBinding
import im.vector.app.features.reactions.data.EmojiDataSource import im.vector.app.features.reactions.data.EmojiDataSource
import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import reactivecircus.flowbinding.android.widget.queryTextChanges
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -167,13 +167,11 @@ class EmojiReactionPickerActivity : VectorBaseActivity<ActivityEmojiReactionPick
} }
searchView.queryTextChanges() searchView.queryTextChanges()
.throttleWithTimeout(600, TimeUnit.MILLISECONDS) .throttleFirst(600)
.doOnError { err -> Timber.e(err) } .onEach { query ->
.observeOn(AndroidSchedulers.mainThread())
.subscribe { query ->
onQueryText(query.toString()) onQueryText(query.toString())
} }
.disposeOnDestroy() .launchIn(lifecycleScope)
} }
searchItem.expandActionView() searchItem.expandActionView()
return true return true

View file

@ -77,8 +77,8 @@ class RequireActiveMembershipViewModel @AssistedInject constructor(
room.flow() room.flow()
.liveRoomSummary() .liveRoomSummary()
.unwrap() .unwrap()
.flowOn(Dispatchers.Default)
.map { mapToLeftViewEvent(room, it) } .map { mapToLeftViewEvent(room, it) }
.flowOn(Dispatchers.Default)
} }
.unwrap() .unwrap()
.onEach { event -> .onEach { event ->

View file

@ -25,7 +25,6 @@ import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.appcompat.queryTextChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
@ -37,12 +36,14 @@ import im.vector.app.core.utils.toast
import im.vector.app.databinding.FragmentPublicRoomsBinding import im.vector.app.databinding.FragmentPublicRoomsBinding
import im.vector.app.features.permalink.NavigationInterceptor import im.vector.app.features.permalink.NavigationInterceptor
import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.permalink.PermalinkHandler
import io.reactivex.rxkotlin.subscribeBy import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import reactivecircus.flowbinding.appcompat.queryTextChanges
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -79,11 +80,11 @@ class PublicRoomsFragment @Inject constructor(
setupRecyclerView() setupRecyclerView()
views.publicRoomsFilter.queryTextChanges() views.publicRoomsFilter.queryTextChanges()
.debounce(500, TimeUnit.MILLISECONDS) .debounce(500)
.subscribeBy { .onEach {
viewModel.handle(RoomDirectoryAction.FilterWith(it.toString())) viewModel.handle(RoomDirectoryAction.FilterWith(it.toString()))
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
views.publicRoomsCreateNewRoom.debouncedClicks { views.publicRoomsCreateNewRoom.debouncedClicks {
sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoom) sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoom)

View file

@ -19,6 +19,7 @@ package im.vector.app.features.roomdirectory
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.viewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -33,6 +34,8 @@ import im.vector.app.features.navigation.Navigator
import im.vector.app.features.roomdirectory.createroom.CreateRoomArgs import im.vector.app.features.roomdirectory.createroom.CreateRoomArgs
import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment
import im.vector.app.features.roomdirectory.picker.RoomDirectoryPickerFragment import im.vector.app.features.roomdirectory.picker.RoomDirectoryPickerFragment
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -55,8 +58,8 @@ class RoomDirectoryActivity : VectorBaseActivity<ActivitySimpleBinding>(), Matri
} }
sharedActionViewModel sharedActionViewModel
.observe() .stream()
.subscribe { sharedAction -> .onEach { sharedAction ->
when (sharedAction) { when (sharedAction) {
is RoomDirectorySharedAction.Back -> popBackstack() is RoomDirectorySharedAction.Back -> popBackstack()
is RoomDirectorySharedAction.CreateRoom -> { is RoomDirectorySharedAction.CreateRoom -> {
@ -74,7 +77,7 @@ class RoomDirectoryActivity : VectorBaseActivity<ActivitySimpleBinding>(), Matri
is RoomDirectorySharedAction.Close -> finish() is RoomDirectorySharedAction.Close -> finish()
} }
} }
.disposeOnDestroy() .launchIn(lifecycleScope)
} }
override fun initUiAndData() { override fun initUiAndData() {

View file

@ -19,6 +19,7 @@ package im.vector.app.features.roomdirectory.createroom
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R import im.vector.app.R
@ -28,6 +29,8 @@ import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
/** /**
* Simple container for [CreateRoomFragment] * Simple container for [CreateRoomFragment]
@ -62,14 +65,14 @@ class CreateRoomActivity : VectorBaseActivity<ActivitySimpleBinding>(), ToolbarC
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
sharedActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
sharedActionViewModel sharedActionViewModel
.observe() .stream()
.subscribe { sharedAction -> .onEach { sharedAction ->
when (sharedAction) { when (sharedAction) {
is RoomDirectorySharedAction.Back, is RoomDirectorySharedAction.Back,
is RoomDirectorySharedAction.Close -> finish() is RoomDirectorySharedAction.Close -> finish()
} }
} }
.disposeOnDestroy() .launchIn(lifecycleScope)
} }
companion object { companion object {

View file

@ -23,6 +23,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
@ -44,6 +45,8 @@ import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel
import im.vector.app.features.roomprofile.settings.joinrule.toOption import im.vector.app.features.roomprofile.settings.joinrule.toOption
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
@ -103,11 +106,11 @@ class CreateRoomFragment @Inject constructor(
private fun setupRoomJoinRuleSharedActionViewModel() { private fun setupRoomJoinRuleSharedActionViewModel() {
roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java) roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java)
roomJoinRuleSharedActionViewModel roomJoinRuleSharedActionViewModel
.observe() .stream()
.subscribe { action -> .onEach { action ->
viewModel.handle(CreateRoomAction.SetVisibility(action.roomJoinRule)) viewModel.handle(CreateRoomAction.SetVisibility(action.roomJoinRule))
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
override fun showFailure(throwable: Throwable) { override fun showFailure(throwable: Throwable) {

View file

@ -20,6 +20,7 @@ package im.vector.app.features.roomprofile
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.viewModel
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
@ -41,6 +42,8 @@ import im.vector.app.features.roomprofile.notifications.RoomNotificationSettings
import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -93,8 +96,8 @@ class RoomProfileActivity :
} }
} }
sharedActionViewModel sharedActionViewModel
.observe() .stream()
.subscribe { sharedAction -> .onEach { sharedAction ->
when (sharedAction) { when (sharedAction) {
RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers()
RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings()
@ -105,7 +108,7 @@ class RoomProfileActivity :
RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings()
}.exhaustive }.exhaustive
} }
.disposeOnDestroy() .launchIn(lifecycleScope)
requireActiveMembershipViewModel.observeViewEvents { requireActiveMembershipViewModel.observeViewEvents {
when (it) { when (it) {

View file

@ -26,6 +26,7 @@ import android.view.ViewGroup
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.setFragmentResultListener import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
@ -52,6 +53,8 @@ import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
@ -124,9 +127,9 @@ class RoomProfileFragment @Inject constructor(
}.exhaustive }.exhaustive
} }
roomListQuickActionsSharedActionViewModel roomListQuickActionsSharedActionViewModel
.observe() .stream()
.subscribe { handleQuickActions(it) } .onEach { handleQuickActions(it) }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
setupClicks() setupClicks()
setupLongClicks() setupLongClicks()
} }

View file

@ -21,6 +21,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
@ -38,6 +39,8 @@ import im.vector.app.features.roomprofile.RoomProfileArgs
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedAction import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedAction
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
@ -77,9 +80,9 @@ class RoomAliasFragment @Inject constructor(
} }
sharedActionViewModel sharedActionViewModel
.observe() .stream()
.subscribe { handleAliasAction(it) } .onEach { handleAliasAction(it) }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun handleAliasAction(action: RoomAliasBottomSheetSharedAction?) { private fun handleAliasAction(action: RoomAliasBottomSheetSharedAction?) {

View file

@ -27,11 +27,9 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -95,7 +93,6 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
if (room.isEncrypted()) { if (room.isEncrypted()) {
room.flow().liveRoomMembers(roomMemberQueryParams) room.flow().liveRoomMembers(roomMemberQueryParams)
.flowOn(Dispatchers.Main)
.flatMapLatest { membersSummary -> .flatMapLatest { membersSummary ->
session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId }) session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId })
.asFlow() .asFlow()

View file

@ -16,7 +16,7 @@
package im.vector.app.features.roomprofile.members package im.vector.app.features.roomprofile.members
import io.reactivex.functions.Predicate import androidx.core.util.Predicate
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import javax.inject.Inject import javax.inject.Inject

View file

@ -24,6 +24,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
@ -46,6 +47,8 @@ import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistory
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleActivity import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleActivity
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import java.util.UUID import java.util.UUID
@ -101,21 +104,21 @@ class RoomSettingsFragment @Inject constructor(
private fun setupRoomJoinRuleSharedActionViewModel() { private fun setupRoomJoinRuleSharedActionViewModel() {
roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java) roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java)
roomJoinRuleSharedActionViewModel roomJoinRuleSharedActionViewModel
.observe() .stream()
.subscribe { action -> .onEach { action ->
viewModel.handle(RoomSettingsAction.SetRoomJoinRule(action.roomJoinRule)) viewModel.handle(RoomSettingsAction.SetRoomJoinRule(action.roomJoinRule))
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun setupRoomHistoryVisibilitySharedActionViewModel() { private fun setupRoomHistoryVisibilitySharedActionViewModel() {
roomHistoryVisibilitySharedActionViewModel = activityViewModelProvider.get(RoomHistoryVisibilitySharedActionViewModel::class.java) roomHistoryVisibilitySharedActionViewModel = activityViewModelProvider.get(RoomHistoryVisibilitySharedActionViewModel::class.java)
roomHistoryVisibilitySharedActionViewModel roomHistoryVisibilitySharedActionViewModel
.observe() .stream()
.subscribe { action -> .onEach { action ->
viewModel.handle(RoomSettingsAction.SetRoomHistoryVisibility(action.roomHistoryVisibility)) viewModel.handle(RoomSettingsAction.SetRoomHistoryVisibility(action.roomHistoryVisibility))
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun showSuccess() { private fun showSuccess() {

View file

@ -40,6 +40,7 @@ import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.RoomProfileArgs
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedActions import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedActions
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedEvents import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedEvents
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedFragment
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedState import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedState
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
import javax.inject.Inject import javax.inject.Inject

View file

@ -14,27 +14,26 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.app.features.roomprofile.settings.joinrule package im.vector.app.features.roomprofile.settings.joinrule.advanced
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.appcompat.queryTextChanges
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSpaceRestrictedSelectBinding import im.vector.app.databinding.FragmentSpaceRestrictedSelectBinding
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roomprofile.settings.joinrule.advanced.ChooseRestrictedController import kotlinx.coroutines.flow.debounce
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedActions import kotlinx.coroutines.flow.launchIn
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel import kotlinx.coroutines.flow.onEach
import io.reactivex.rxkotlin.subscribeBy
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
import java.util.concurrent.TimeUnit import reactivecircus.flowbinding.appcompat.queryTextChanges
import javax.inject.Inject import javax.inject.Inject
class RoomJoinRuleChooseRestrictedFragment @Inject constructor( class RoomJoinRuleChooseRestrictedFragment @Inject constructor(
@ -54,11 +53,11 @@ class RoomJoinRuleChooseRestrictedFragment @Inject constructor(
controller.listener = this controller.listener = this
views.recyclerView.configureWith(controller) views.recyclerView.configureWith(controller)
views.roomsFilter.queryTextChanges() views.roomsFilter.queryTextChanges()
.debounce(500, TimeUnit.MILLISECONDS) .debounce(500)
.subscribeBy { .onEach {
viewModel.handle(RoomJoinRuleChooseRestrictedActions.FilterWith(it.toString())) viewModel.handle(RoomJoinRuleChooseRestrictedActions.FilterWith(it.toString()))
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
views.okButton.debouncedClicks { views.okButton.debouncedClicks {
parentFragmentManager.popBackStack() parentFragmentManager.popBackStack()

View file

@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NA
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.rx.SecretsSynchronisationInfo
data class SecretsSynchronisationInfo( data class SecretsSynchronisationInfo(
val isBackupSetup: Boolean, val isBackupSetup: Boolean,

View file

@ -27,8 +27,6 @@ import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import timber.log.Timber import timber.log.Timber
@ -67,31 +65,10 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat() {
mLoadingView = vectorActivity.findViewById(R.id.vector_settings_spinner_views) mLoadingView = vectorActivity.findViewById(R.id.vector_settings_spinner_views)
} }
@CallSuper
override fun onDestroyView() {
uiDisposables.clear()
super.onDestroyView()
}
override fun onDestroy() {
uiDisposables.dispose()
super.onDestroy()
}
abstract fun bindPref() abstract fun bindPref()
abstract var titleRes: Int abstract var titleRes: Int
/* ==========================================================================================
* Disposable
* ========================================================================================== */
private val uiDisposables = CompositeDisposable()
protected fun Disposable.disposeOnDestroyView() {
uiDisposables.add(this)
}
/* ========================================================================================== /* ==========================================================================================
* Protected * Protected
* ========================================================================================== */ * ========================================================================================== */

View file

@ -58,9 +58,6 @@ import im.vector.app.features.pin.PinMode
import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.getElementWellknown
import im.vector.app.features.raw.wellknown.isE2EByDefault import im.vector.app.features.raw.wellknown.isE2EByDefault
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import io.reactivex.disposables.Disposable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -71,7 +68,6 @@ import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
import org.matrix.android.sdk.rx.SecretsSynchronisationInfo
import javax.inject.Inject import javax.inject.Inject
class VectorSettingsSecurityPrivacyFragment @Inject constructor( class VectorSettingsSecurityPrivacyFragment @Inject constructor(
@ -86,7 +82,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
override var titleRes = R.string.settings_security_and_privacy override var titleRes = R.string.settings_security_and_privacy
override val preferenceXmlRes = R.xml.vector_settings_security_privacy override val preferenceXmlRes = R.xml.vector_settings_security_privacy
private var disposables = mutableListOf<Disposable>()
// cryptography // cryptography
private val mCryptographyCategory by lazy { private val mCryptographyCategory by lazy {
@ -149,11 +144,11 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
refreshMyDevice() refreshMyDevice()
refreshXSigningStatus() refreshXSigningStatus()
session.liveSecretSynchronisationInfo() session.liveSecretSynchronisationInfo()
.flowOn(Dispatchers.Main)
.onEach { .onEach {
refresh4SSection(it) refresh4SSection(it)
refreshXSigningStatus() refreshXSigningStatus()
}.launchIn(viewLifecycleOwner.lifecycleScope) }
.launchIn(viewLifecycleOwner.lifecycleScope)
lifecycleScope.launchWhenResumed { lifecycleScope.launchWhenResumed {
findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible = findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible =
@ -173,14 +168,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
// findPreference<VectorPreference>(VectorPreferences.SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY) // findPreference<VectorPreference>(VectorPreferences.SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY)
// } // }
override fun onPause() {
super.onPause()
disposables.forEach {
it.dispose()
}
disposables.clear()
}
private fun refresh4SSection(info: SecretsSynchronisationInfo) { private fun refresh4SSection(info: SecretsSynchronisationInfo) {
// it's a lot of if / else if / else // it's a lot of if / else if / else
// But it's not yet clear how to manage all cases // But it's not yet clear how to manage all cases

View file

@ -29,11 +29,12 @@ import dagger.assisted.AssistedInject
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.flow.throttleFirst
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.PublishDataSource
import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.auth.ReAuthActivity
import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.ReAuthHelper
import io.reactivex.subjects.PublishSubject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
@ -64,7 +65,6 @@ import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -103,7 +103,7 @@ class DevicesViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory<DevicesViewModel, DevicesViewState> by hiltMavericksViewModelFactory() companion object : MavericksViewModelFactory<DevicesViewModel, DevicesViewState> by hiltMavericksViewModelFactory()
private val refreshPublisher: PublishSubject<Unit> = PublishSubject.create() private val refreshSource = PublishDataSource<Unit>()
init { init {
@ -166,12 +166,12 @@ class DevicesViewModel @AssistedInject constructor(
// ) // )
// } // }
refreshPublisher.throttleFirst(4_000, TimeUnit.MILLISECONDS) refreshSource.stream().throttleFirst(4_000)
.subscribe { .onEach {
session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
session.cryptoService().downloadKeys(listOf(session.myUserId), true, NoOpMatrixCallback()) session.cryptoService().downloadKeys(listOf(session.myUserId), true, NoOpMatrixCallback())
} }
.disposeOnClear() .launchIn(viewModelScope)
// then force download // then force download
queryRefreshDevicesList() queryRefreshDevicesList()
} }
@ -193,7 +193,7 @@ class DevicesViewModel @AssistedInject constructor(
* It can be any mobile devices, and any browsers. * It can be any mobile devices, and any browsers.
*/ */
private fun queryRefreshDevicesList() { private fun queryRefreshDevicesList() {
refreshPublisher.onNext(Unit) refreshSource.post(Unit)
} }
override fun handle(action: DevicesAction) { override fun handle(action: DevicesAction) {

View file

@ -50,7 +50,7 @@ class SoftLogoutActivity : LoginActivity() {
override fun initUiAndData() { override fun initUiAndData() {
super.initUiAndData() super.initUiAndData()
softLogoutViewModel.subscribe(this) { softLogoutViewModel.onEach {
updateWithState(it) updateWithState(it)
} }

View file

@ -52,7 +52,7 @@ class SoftLogoutActivity2 : LoginActivity2() {
override fun initUiAndData() { override fun initUiAndData() {
super.initUiAndData() super.initUiAndData()
softLogoutViewModel.subscribe(this) { softLogoutViewModel.onEach {
updateWithState(it) updateWithState(it)
} }

View file

@ -55,7 +55,7 @@ class SoftLogoutFragment @Inject constructor(
setupRecyclerView() setupRecyclerView()
softLogoutViewModel.subscribe(this) { softLogoutViewState -> softLogoutViewModel.onEach { softLogoutViewState ->
softLogoutController.update(softLogoutViewState) softLogoutController.update(softLogoutViewState)
when (val mode = softLogoutViewState.asyncHomeServerLoginFlowRequest.invoke()) { when (val mode = softLogoutViewState.asyncHomeServerLoginFlowRequest.invoke()) {
is LoginMode.SsoAndPassword -> { is LoginMode.SsoAndPassword -> {

View file

@ -26,12 +26,12 @@ import android.view.ViewGroup
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.checkedChanges
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
@ -43,10 +43,12 @@ import im.vector.app.core.utils.styleMatchingText
import im.vector.app.databinding.BottomSheetLeaveSpaceBinding import im.vector.app.databinding.BottomSheetLeaveSpaceBinding
import im.vector.app.features.displayname.getBestName import im.vector.app.features.displayname.getBestName
import im.vector.app.features.spaces.leave.SpaceLeaveAdvancedActivity import im.vector.app.features.spaces.leave.SpaceLeaveAdvancedActivity
import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import me.gujun.android.span.span import me.gujun.android.span.span
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import reactivecircus.flowbinding.android.widget.checkedChanges
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -82,8 +84,7 @@ class LeaveSpaceBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetLea
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
views.autoLeaveRadioGroup.checkedChanges() views.autoLeaveRadioGroup.checkedChanges()
.observeOn(AndroidSchedulers.mainThread()) .onEach {
.subscribe {
when (it) { when (it) {
views.leaveAll.id -> { views.leaveAll.id -> {
settingsViewModel.handle(SpaceLeaveViewAction.SetAutoLeaveAll) settingsViewModel.handle(SpaceLeaveViewAction.SetAutoLeaveAll)
@ -100,7 +101,7 @@ class LeaveSpaceBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetLea
} }
} }
} }
.disposeOnDestroyView() .launchIn(viewLifecycleOwner.lifecycleScope)
views.leaveButton.debouncedClicks { views.leaveButton.debouncedClicks {
settingsViewModel.handle(SpaceLeaveViewAction.LeaveSpace) settingsViewModel.handle(SpaceLeaveViewAction.LeaveSpace)

View file

@ -71,7 +71,7 @@ class SpaceCreationActivity : SimpleFragmentActivity() {
override fun initUiAndData() { override fun initUiAndData() {
super.initUiAndData() super.initUiAndData()
viewModel.subscribe(this) { viewModel.onEach {
renderState(it) renderState(it)
} }

View file

@ -35,6 +35,7 @@ import im.vector.app.group
import im.vector.app.space import im.vector.app.space
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -89,14 +90,11 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
// observeSelectionState() // observeSelectionState()
appStateHandler.selectedRoomGroupingObservable appStateHandler.selectedRoomGroupingObservable
.distinctUntilChanged() .distinctUntilChanged()
.subscribe { .setOnEach {
setState { copy(
copy( selectedGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null)
selectedGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null) )
)
}
} }
.disposeOnClear()
session.getGroupSummariesLive(groupSummaryQueryParams {}) session.getGroupSummariesLive(groupSummaryQueryParams {})
.asFlow() .asFlow()
@ -114,7 +112,6 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
}, sortOrder = RoomSortOrder.NONE }, sortOrder = RoomSortOrder.NONE
).asFlow() ).asFlow()
.sample(300) .sample(300)
.flowOn(Dispatchers.Default)
.onEach { .onEach {
val inviteCount = if (autoAcceptInvites.hideInvites) { val inviteCount = if (autoAcceptInvites.hideInvites) {
0 0
@ -140,7 +137,9 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
homeAggregateCount = counts homeAggregateCount = counts
) )
} }
}.launchIn(viewModelScope) }
.flowOn(Dispatchers.Default)
.launchIn(viewModelScope)
} }
override fun handle(action: SpaceListAction) { override fun handle(action: SpaceListAction) {

View file

@ -19,6 +19,7 @@ package im.vector.app.features.spaces
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Mavericks
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R import im.vector.app.R
@ -27,6 +28,8 @@ import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.spaces.preview.SpacePreviewArgs import im.vector.app.features.spaces.preview.SpacePreviewArgs
import im.vector.app.features.spaces.preview.SpacePreviewFragment import im.vector.app.features.spaces.preview.SpacePreviewFragment
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@AndroidEntryPoint @AndroidEntryPoint
class SpacePreviewActivity : VectorBaseActivity<ActivitySimpleBinding>() { class SpacePreviewActivity : VectorBaseActivity<ActivitySimpleBinding>() {
@ -39,8 +42,8 @@ class SpacePreviewActivity : VectorBaseActivity<ActivitySimpleBinding>() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
sharedActionViewModel = viewModelProvider.get(SpacePreviewSharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(SpacePreviewSharedActionViewModel::class.java)
sharedActionViewModel sharedActionViewModel
.observe() .stream()
.subscribe { action -> .onEach { action ->
when (action) { when (action) {
SpacePreviewSharedAction.DismissAction -> finish() SpacePreviewSharedAction.DismissAction -> finish()
SpacePreviewSharedAction.ShowModalLoading -> showWaitingView() SpacePreviewSharedAction.ShowModalLoading -> showWaitingView()
@ -48,7 +51,7 @@ class SpacePreviewActivity : VectorBaseActivity<ActivitySimpleBinding>() {
is SpacePreviewSharedAction.ShowErrorMessage -> action.error?.let { showSnackbar(it) } is SpacePreviewSharedAction.ShowErrorMessage -> action.error?.let { showSnackbar(it) }
} }
} }
.disposeOnDestroy() .launchIn(lifecycleScope)
if (isFirstCreation()) { if (isFirstCreation()) {
val simpleName = SpacePreviewFragment::class.java.simpleName val simpleName = SpacePreviewFragment::class.java.simpleName

View file

@ -50,7 +50,7 @@ class CreateSpaceAdd3pidInvitesFragment @Inject constructor(
views.recyclerView.configureWith(epoxyController) views.recyclerView.configureWith(epoxyController)
epoxyController.listener = this epoxyController.listener = this
sharedViewModel.subscribe(this) { sharedViewModel.onEach {
invalidateState(it) invalidateState(it)
} }

View file

@ -46,7 +46,7 @@ class CreateSpaceDefaultRoomsFragment @Inject constructor(
views.recyclerView.configureWith(epoxyController) views.recyclerView.configureWith(epoxyController)
epoxyController.listener = this epoxyController.listener = this
sharedViewModel.subscribe(this) { sharedViewModel.onEach {
epoxyController.setData(it) epoxyController.setData(it)
} }

View file

@ -50,7 +50,7 @@ class CreateSpaceDetailsFragment @Inject constructor(
views.recyclerView.configureWith(epoxyController) views.recyclerView.configureWith(epoxyController)
epoxyController.listener = this epoxyController.listener = this
sharedViewModel.subscribe(this) { sharedViewModel.onEach {
epoxyController.setData(it) epoxyController.setData(it)
} }

View file

@ -16,6 +16,7 @@
package im.vector.app.features.spaces.leave package im.vector.app.features.spaces.leave
import androidx.core.util.Predicate
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
@ -27,7 +28,6 @@ import im.vector.app.core.epoxy.noResultItem
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.spaces.manage.roomSelectionItem import im.vector.app.features.spaces.manage.roomSelectionItem
import io.reactivex.functions.Predicate
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem

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