From 34cb99e8aec12526bc242f8238787443fb5f6564 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 27 Oct 2021 12:13:49 +0200 Subject: [PATCH] Flow migration: remove Rx completely (rxbinding) --- dependencies.gradle | 12 +-- vector/build.gradle | 12 +-- .../java/im/vector/app/AppStateHandler.kt | 2 - .../im/vector/app/core/di/FragmentModule.kt | 2 +- .../{ChunkOperator.kt => TimingOperators.kt} | 7 +- .../app/core/platform/VectorBaseActivity.kt | 24 ++---- .../VectorBaseBottomSheetDialogFragment.kt | 29 ++----- .../app/core/platform/VectorBaseFragment.kt | 28 ++----- .../java/im/vector/app/core/rx/RxConfig.kt | 18 +--- .../im/vector/app/core/utils/CountUpTimer.kt | 33 +++++--- .../im/vector/app/core/utils/DataSource.kt | 9 -- .../app/core/utils/DefaultSubscriber.kt | 31 ------- .../app/features/call/webrtc/WebRtcCall.kt | 82 +++++++++++-------- .../contactsbook/ContactsBookFragment.kt | 18 ++-- .../quads/SharedSecuredStorageKeyFragment.kt | 19 +++-- .../SharedSecuredStoragePassphraseFragment.kt | 19 +++-- .../BootstrapConfirmPassphraseFragment.kt | 21 +++-- .../BootstrapEnterPassphraseFragment.kt | 21 +++-- .../recover/BootstrapMigrateBackupFragment.kt | 19 +++-- .../devtools/RoomDevToolEditFragment.kt | 9 +- .../change/SetIdentityServerFragment.kt | 9 +- .../app/features/home/HomeDetailViewModel.kt | 1 - .../home/room/detail/RoomDetailFragment.kt | 16 ++-- .../reactions/ViewReactionsViewModel.kt | 2 - .../room/list/RoomListDisplayModeFilter.kt | 2 +- .../home/room/list/RoomListNameFilter.kt | 2 +- .../room/list/RoomListSectionBuilderSpace.kt | 4 - .../app/features/invite/InvitesAcceptor.kt | 1 - .../app/features/login/LoginFragment.kt | 27 +++--- .../LoginGenericTextInputFormFragment.kt | 13 +-- .../login/LoginResetPasswordFragment.kt | 28 +++---- .../login/LoginServerUrlFormFragment.kt | 9 +- .../login2/LoginFragmentSigninPassword2.kt | 11 ++- .../login2/LoginFragmentSigninUsername2.kt | 11 ++- .../login2/LoginFragmentSignupPassword2.kt | 10 ++- .../login2/LoginFragmentSignupUsername2.kt | 11 ++- .../features/login2/LoginFragmentToAny2.kt | 27 +++--- .../LoginGenericTextInputFormFragment2.kt | 15 ++-- .../login2/LoginResetPasswordFragment2.kt | 28 +++---- .../login2/LoginServerUrlFormFragment2.kt | 9 +- .../reactions/EmojiReactionPickerActivity.kt | 13 +-- .../roomdirectory/PublicRoomsFragment.kt | 12 +-- .../members/RoomMemberSummaryFilter.kt | 2 +- .../settings/joinrule/RoomJoinRuleActivity.kt | 1 + .../RoomJoinRuleChooseRestrictedFragment.kt | 18 ++-- .../settings/VectorSettingsBaseFragment.kt | 23 ------ .../VectorSettingsSecurityPrivacyFragment.kt | 10 --- .../settings/devices/DevicesViewModel.kt | 1 - .../features/spaces/LeaveSpaceBottomSheet.kt | 10 ++- .../spaces/leave/SelectChildrenController.kt | 2 +- .../leave/SpaceLeaveAdvancedFragment.kt | 13 +-- .../spaces/manage/SpaceAddRoomFragment.kt | 13 +-- .../manage/SpaceChildInfoMatchFilter.kt | 2 +- .../spaces/manage/SpaceManageRoomsFragment.kt | 13 +-- .../spaces/people/SpacePeopleFragment.kt | 13 +-- .../spaces/preview/SpacePreviewFragment.kt | 18 ++-- .../userdirectory/UserListFragment.kt | 12 ++- 57 files changed, 392 insertions(+), 435 deletions(-) rename vector/src/main/java/im/vector/app/core/flow/{ChunkOperator.kt => TimingOperators.kt} (90%) delete mode 100644 vector/src/main/java/im/vector/app/core/utils/DefaultSubscriber.kt diff --git a/dependencies.gradle b/dependencies.gradle index 15660f17f2..8b9ebb4e75 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -17,7 +17,7 @@ def arrow = "0.8.2" def markwon = "4.6.2" def moshi = "1.12.0" def lifecycle = "2.2.0" -def rxBinding = "3.1.0" +def flowBinding = "1.2.0" def epoxy = "4.6.2" def mavericks = "2.4.0" def glide = "4.12.0" @@ -115,13 +115,13 @@ ext.libs = [ 'bigImageViewer' : "com.github.piasy:BigImageViewer:$bigImageViewer", 'glideImageLoader' : "com.github.piasy:GlideImageLoader:$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 : [ - '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" + 'timber' : "com.jakewharton.timber:timber:5.0.1" ], jsonwebtoken: [ 'jjwtApi' : "io.jsonwebtoken:jjwt-api:$jjwt", diff --git a/vector/build.gradle b/vector/build.gradle index 493a26dcc6..2b616335db 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -373,14 +373,10 @@ dependencies { // Phone number https://github.com/google/libphonenumber implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.35' - // rx - implementation libs.rx.rxKotlin - implementation libs.rx.rxAndroid - implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.1' - // RXBinding - implementation libs.jakewharton.rxbinding - implementation libs.jakewharton.rxbindingAppcompat - implementation libs.jakewharton.rxbindingMaterial + // FlowBinding + implementation libs.github.flowBinding + implementation libs.github.flowBindingAppcompat + implementation libs.github.flowBindingMaterial implementation libs.airbnb.epoxy implementation libs.airbnb.epoxyGlide diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index 650047787e..b6d41ce35d 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -24,7 +24,6 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.utils.BehaviorDataSource import im.vector.app.features.session.coroutineScope import im.vector.app.features.ui.UiStateRepository -import io.reactivex.disposables.CompositeDisposable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -37,7 +36,6 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.api.util.CancelableBag import javax.inject.Inject import javax.inject.Singleton diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index bf72dcb076..0291765b59 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -111,7 +111,7 @@ import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment 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.advanced.RoomJoinRuleChooseRestrictedFragment import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment diff --git a/vector/src/main/java/im/vector/app/core/flow/ChunkOperator.kt b/vector/src/main/java/im/vector/app/core/flow/TimingOperators.kt similarity index 90% rename from vector/src/main/java/im/vector/app/core/flow/ChunkOperator.kt rename to vector/src/main/java/im/vector/app/core/flow/TimingOperators.kt index 533a060883..edd77f6935 100644 --- a/vector/src/main/java/im/vector/app/core/flow/ChunkOperator.kt +++ b/vector/src/main/java/im/vector/app/core/flow/TimingOperators.kt @@ -26,13 +26,14 @@ 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 Flow.chunk(durationInMillis: Long): Flow> { - require(durationInMillis> 0) { "Duration should be greater than 0" } + require(durationInMillis > 0) { "Duration should be greater than 0" } return flow { coroutineScope { val events = ArrayList() @@ -66,6 +67,10 @@ fun Flow.chunk(durationInMillis: Long): Flow> { } } +fun tickerFlow(scope: CoroutineScope, delayMillis: Long, initialDelayMillis: Long = delayMillis): Flow { + return scope.fixedPeriodTicker(delayMillis, initialDelayMillis).consumeAsFlow() +} + private fun CoroutineScope.fixedPeriodTicker(delayMillis: Long, initialDelayMillis: Long = delayMillis): ReceiveChannel { require(delayMillis >= 0) { "Expected non-negative delay, but has $delayMillis ms" } require(initialDelayMillis >= 0) { "Expected non-negative initial delay, but has $initialDelayMillis ms" } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index a28d9fa355..4f51317e2f 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -45,7 +45,6 @@ import com.airbnb.mvrx.MavericksView import com.bumptech.glide.util.Util import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.snackbar.Snackbar -import com.jakewharton.rxbinding3.view.clicks import dagger.hilt.android.EntryPointAccessors import im.vector.app.BuildConfig import im.vector.app.R @@ -78,17 +77,13 @@ import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ThemeUtils import im.vector.app.receivers.DebugReceiver -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.disposables.Disposable -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.GlobalError +import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject abstract class VectorBaseActivity : AppCompatActivity(), MavericksView { @@ -123,10 +118,9 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver protected fun View.debouncedClicks(onClicked: () -> Unit) { clicks() - .throttleFirst(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { onClicked() } - .disposeOnDestroy() + .sample(300) + .onEach { onClicked() } + .launchIn(lifecycleScope) } /* ========================================================================================== @@ -137,6 +131,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver private lateinit var sessionListener: SessionListener protected lateinit var bugReporter: BugReporter private lateinit var pinLocker: PinLocker + @Inject lateinit var rageShake: RageShake lateinit var navigator: Navigator @@ -154,7 +149,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver // For debug only private var debugReceiver: DebugReceiver? = null - private val uiDisposables = CompositeDisposable() private val restorables = ArrayList() override fun attachBaseContext(base: Context) { @@ -179,10 +173,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver return this } - protected fun Disposable.disposeOnDestroy() { - uiDisposables.add(this) - } - @CallSuper override fun onCreate(savedInstanceState: Bundle?) { Timber.i("onCreate Activity ${javaClass.simpleName}") @@ -306,8 +296,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver override fun onDestroy() { super.onDestroy() Timber.i("onDestroy Activity ${javaClass.simpleName}") - - uiDisposables.dispose() } private val pinStartForActivityResult = registerStartForActivityResult { activityResult -> diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt index 04a34a8876..1304c31177 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -33,19 +33,14 @@ import com.airbnb.mvrx.MavericksView import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import com.jakewharton.rxbinding3.view.clicks import dagger.hilt.android.EntryPointAccessors import im.vector.app.core.di.ActivityEntryPoint import im.vector.app.core.utils.DimensionConverter -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.disposables.Disposable -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample +import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber -import java.util.concurrent.TimeUnit /** * Add Mavericks capabilities, handle DI and bindings. @@ -113,14 +108,12 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe @CallSuper override fun onDestroyView() { - uiDisposables.clear() _binding = null super.onDestroyView() } @CallSuper override fun onDestroy() { - uiDisposables.dispose() super.onDestroy() } @@ -169,27 +162,15 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe 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 * ========================================================================================== */ protected fun View.debouncedClicks(onClicked: () -> Unit) { clicks() - .throttleFirst(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { onClicked() } - .disposeOnDestroyView() + .sample(300) + .onEach { onClicked() } + .launchIn(viewLifecycleOwner.lifecycleScope) } /* ========================================================================================== diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index 7dce2bc954..0c3494bc40 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt @@ -35,7 +35,6 @@ import com.airbnb.mvrx.MavericksView import com.bumptech.glide.util.Util.assertMainThread import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.jakewharton.rxbinding3.view.clicks import dagger.hilt.android.EntryPointAccessors import im.vector.app.R import im.vector.app.core.di.ActivityEntryPoint @@ -46,14 +45,10 @@ import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.features.navigation.Navigator import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.disposables.Disposable -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample +import reactivecircus.flowbinding.android.view.clicks import timber.log.Timber import java.util.concurrent.TimeUnit @@ -155,7 +150,6 @@ abstract class VectorBaseFragment : Fragment(), MavericksView @CallSuper override fun onDestroyView() { Timber.i("onDestroyView Fragment ${javaClass.simpleName}") - uiDisposables.clear() _binding = null super.onDestroyView() } @@ -163,7 +157,6 @@ abstract class VectorBaseFragment : Fragment(), MavericksView @CallSuper override fun onDestroy() { Timber.i("onDestroy Fragment ${javaClass.simpleName}") - uiDisposables.dispose() super.onDestroy() } @@ -228,16 +221,6 @@ abstract class VectorBaseFragment : Fragment(), MavericksView } } - /* ========================================================================================== - * Disposable - * ========================================================================================== */ - - private val uiDisposables = CompositeDisposable() - - protected fun Disposable.disposeOnDestroyView() { - uiDisposables.add(this) - } - /* ========================================================================================== * ViewEvents * ========================================================================================== */ @@ -258,10 +241,9 @@ abstract class VectorBaseFragment : Fragment(), MavericksView protected fun View.debouncedClicks(onClicked: () -> Unit) { clicks() - .throttleFirst(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { onClicked() } - .disposeOnDestroyView() + .sample(300) + .onEach { onClicked() } + .launchIn(viewLifecycleOwner.lifecycleScope) } /* ========================================================================================== diff --git a/vector/src/main/java/im/vector/app/core/rx/RxConfig.kt b/vector/src/main/java/im/vector/app/core/rx/RxConfig.kt index ab0ffb7802..41ceff7e64 100644 --- a/vector/src/main/java/im/vector/app/core/rx/RxConfig.kt +++ b/vector/src/main/java/im/vector/app/core/rx/RxConfig.kt @@ -16,28 +16,14 @@ 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 -) { +class RxConfig @Inject constructor() { /** * 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 - } - } - } + } } diff --git a/vector/src/main/java/im/vector/app/core/utils/CountUpTimer.kt b/vector/src/main/java/im/vector/app/core/utils/CountUpTimer.kt index a029f4eec9..b58d0fb3f6 100644 --- a/vector/src/main/java/im/vector/app/core/utils/CountUpTimer.kt +++ b/vector/src/main/java/im/vector/app/core/utils/CountUpTimer.kt @@ -16,23 +16,36 @@ package im.vector.app.core.utils -import io.reactivex.Observable -import java.util.concurrent.TimeUnit +import im.vector.app.core.flow.tickerFlow +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.AtomicLong class CountUpTimer(private val intervalInMs: Long = 1_000) { + private val coroutineScope = CoroutineScope(Dispatchers.Main) private val elapsedTime: AtomicLong = AtomicLong() private val resumed: AtomicBoolean = AtomicBoolean(false) - private val disposable = Observable.interval(intervalInMs / 10, TimeUnit.MILLISECONDS) - .filter { resumed.get() } - .map { elapsedTime.addAndGet(intervalInMs / 10) } - .filter { it % intervalInMs == 0L } - .subscribe { - tickListener?.onTick(it) - } + init { + startCounter() + } + + private fun startCounter() { + 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 @@ -49,7 +62,7 @@ class CountUpTimer(private val intervalInMs: Long = 1_000) { } fun stop() { - disposable.dispose() + coroutineScope.cancel() } interface TickListener { diff --git a/vector/src/main/java/im/vector/app/core/utils/DataSource.kt b/vector/src/main/java/im/vector/app/core/utils/DataSource.kt index 6338768723..f83eda68e9 100644 --- a/vector/src/main/java/im/vector/app/core/utils/DataSource.kt +++ b/vector/src/main/java/im/vector/app/core/utils/DataSource.kt @@ -16,7 +16,6 @@ package im.vector.app.core.utils -import com.jakewharton.rxrelay2.BehaviorRelay import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -46,14 +45,6 @@ open class BehaviorDataSource(private val defaultValue: T? = null) : MutableD override fun post(value: T) { mutableFlow.tryEmit(value) } - - private fun createRelay(): BehaviorRelay { - return if (defaultValue == null) { - BehaviorRelay.create() - } else { - BehaviorRelay.createDefault(defaultValue) - } - } } /** diff --git a/vector/src/main/java/im/vector/app/core/utils/DefaultSubscriber.kt b/vector/src/main/java/im/vector/app/core/utils/DefaultSubscriber.kt deleted file mode 100644 index a82e5a4e03..0000000000 --- a/vector/src/main/java/im/vector/app/core/utils/DefaultSubscriber.kt +++ /dev/null @@ -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 Single.subscribeLogError(): Disposable { - return subscribe(Functions.emptyConsumer(), { Timber.e(it) }) -} - -fun Completable.subscribeLogError(): Disposable { - return subscribe({}, { Timber.e(it) }) -} diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt index e632d00790..bbb158f6e4 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt @@ -19,8 +19,10 @@ package im.vector.app.features.call.webrtc import android.content.Context import android.hardware.camera2.CameraManager import androidx.core.content.getSystemService +import im.vector.app.core.flow.chunk import im.vector.app.core.services.CallService 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.features.call.CameraEventsHandlerAdapter 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.mapToCallCandidate 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.Deferred import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.async 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.withContext import org.matrix.android.sdk.api.extensions.orFalse @@ -85,7 +89,6 @@ import org.webrtc.VideoTrack import timber.log.Timber import java.lang.ref.WeakReference import java.util.concurrent.CopyOnWriteArrayList -import java.util.concurrent.TimeUnit import javax.inject.Provider import kotlin.coroutines.CoroutineContext @@ -157,7 +160,7 @@ class WebRtcCall( private var currentCaptureFormat: CaptureFormat = CaptureFormat.HD 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 { override fun onTick(milliseconds: Long) { val formattedDuration = formatDuration(Duration.ofMillis(milliseconds)) @@ -197,26 +200,33 @@ class WebRtcCall( private var localSurfaceRenderers: MutableList> = ArrayList() private var remoteSurfaceRenderers: MutableList> = ArrayList() - private val iceCandidateSource: PublishSubject = PublishSubject.create() - private val iceCandidateDisposable = iceCandidateSource - .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 localIceCandidateSource = PublishDataSource() + private var localIceCandidateJob: Job? = null - private val remoteCandidateSource: ReplaySubject = ReplaySubject.create() - private var remoteIceCandidateDisposable: Disposable? = null + private val remoteCandidateSource: MutableSharedFlow = MutableSharedFlow(replay = Int.MAX_VALUE) + private var remoteIceCandidateJob: Job? = null init { + setupLocalIceCanditate() 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) { sessionScope?.launch(dispatcher) { @@ -438,12 +448,15 @@ class WebRtcCall( createLocalStream() attachViewRenderersInternal() Timber.tag(loggerTag.value).v("remoteCandidateSource $remoteCandidateSource") - remoteIceCandidateDisposable = remoteCandidateSource.subscribe({ - 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") - }) + remoteIceCandidateJob = remoteCandidateSource + .onEach { + Timber.tag(loggerTag.value).v("adding remote ice candidate $it") + peerConnection?.addIceCandidate(it) + } + .catch { + Timber.tag(loggerTag.value).v("failed to add remote ice candidate $it") + } + .launchIn(this) // Now we wait for negotiation callback } @@ -488,12 +501,13 @@ class WebRtcCall( mxCall.accept(it.description) } Timber.tag(loggerTag.value).v("remoteCandidateSource $remoteCandidateSource") - remoteIceCandidateDisposable = remoteCandidateSource.subscribe({ - 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") - }) + remoteIceCandidateJob = remoteCandidateSource + .onEach { + Timber.tag(loggerTag.value).v("adding remote ice candidate $it") + peerConnection?.addIceCandidate(it) + }.catch { + Timber.tag(loggerTag.value).v("failed to add remote ice candidate $it") + }.launchIn(this) } private suspend fun getTurnServer(): TurnServerResponse? { @@ -761,8 +775,8 @@ class WebRtcCall( videoCapturer?.stopCapture() videoCapturer?.dispose() videoCapturer = null - remoteIceCandidateDisposable?.dispose() - iceCandidateDisposable?.dispose() + remoteIceCandidateJob?.cancel() + localIceCandidateJob?.cancel() peerConnection?.close() peerConnection?.dispose() localAudioSource?.dispose() @@ -852,7 +866,7 @@ class WebRtcCall( } Timber.tag(loggerTag.value).v("onCallIceCandidateReceived for call ${mxCall.callId} sdp: ${it.candidate}") val iceCandidate = IceCandidate(it.sdpMid, it.sdpMLineIndex, it.candidate) - remoteCandidateSource.onNext(iceCandidate) + remoteCandidateSource.emit(iceCandidate) } } } diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt index d79ad308de..a02e4d6b89 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt @@ -21,10 +21,9 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel 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.configureWith import im.vector.app.core.extensions.hideKeyboard @@ -37,8 +36,13 @@ import im.vector.app.features.userdirectory.UserListAction import im.vector.app.features.userdirectory.UserListSharedAction import im.vector.app.features.userdirectory.UserListSharedActionViewModel 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.user.model.User +import reactivecircus.flowbinding.android.widget.checkedChanges +import reactivecircus.flowbinding.android.widget.textChanges import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -83,21 +87,21 @@ class ContactsBookFragment @Inject constructor( private fun setupOnlyBoundContactsView() { views.phoneBookOnlyBoundContacts.checkedChanges() - .subscribe { + .onEach { contactsBookViewModel.handle(ContactsBookAction.OnlyBoundContacts(it)) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun setupFilterView() { views.phoneBookFilter .textChanges() .skipInitialValue() - .debounce(300, TimeUnit.MILLISECONDS) - .subscribe { + .debounce(300) + .onEach { contactsBookViewModel.handle(ContactsBookAction.FilterWith(it.toString())) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } override fun onDestroyView() { diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt index 1ba0198cb4..fbfb538fe3 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt @@ -22,16 +22,20 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo +import androidx.lifecycle.lifecycleScope 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.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.startImportTextFromFileIntent import im.vector.app.databinding.FragmentSsssAccessFromKeyBinding import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.extensions.tryOrNull +import reactivecircus.flowbinding.android.widget.editorActionEvents +import reactivecircus.flowbinding.android.widget.textChanges import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -48,22 +52,21 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment views.ssssRestoreWithKeyText.text = getString(R.string.enter_secret_storage_input_key) views.ssssKeyEnterEdittext.editorActionEvents() - .throttleFirst(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { + .sample(300) + .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() } } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) views.ssssKeyEnterEdittext.textChanges() .skipInitialValue() - .subscribe { + .onEach { views.ssssKeyEnterTil.error = null views.ssssKeySubmit.isEnabled = it.isNotBlank() } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) views.ssssKeyUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt index 800b02a936..4441268d30 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt @@ -22,14 +22,18 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo import androidx.core.text.toSpannable +import androidx.lifecycle.lifecycleScope 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.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample +import reactivecircus.flowbinding.android.widget.editorActionEvents +import reactivecircus.flowbinding.android.widget.textChanges import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -60,21 +64,20 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor( // .colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) views.ssssPassphraseEnterEdittext.editorActionEvents() - .throttleFirst(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { + .sample(300) + .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() } } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) views.ssssPassphraseEnterEdittext.textChanges() - .subscribe { + .onEach { views.ssssPassphraseEnterTil.error = null views.ssssPassphraseSubmit.isEnabled = it.isNotBlank() } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) views.ssssPassphraseReset.views.bottomSheetActionClickableZone.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll) diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt index 4aeb822bbd..48191b08d6 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt @@ -22,15 +22,19 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo import androidx.core.view.isGone +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.parentFragmentViewModel 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.core.extensions.hideKeyboard import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample +import reactivecircus.flowbinding.android.widget.editorActionEvents +import reactivecircus.flowbinding.android.widget.textChanges import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -58,21 +62,20 @@ class BootstrapConfirmPassphraseFragment @Inject constructor() : } views.ssssPassphraseEnterEdittext.editorActionEvents() - .throttleFirst(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { + .sample(300) + .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() } } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) views.ssssPassphraseEnterEdittext.textChanges() - .subscribe { + .onEach { views.ssssPassphraseEnterTil.error = null - sharedViewModel.handle(BootstrapActions.UpdateConfirmCandidatePassphrase(it?.toString() ?: "")) + sharedViewModel.handle(BootstrapActions.UpdateConfirmCandidatePassphrase(it.toString())) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) sharedViewModel.observeViewEvents { // when (it) { diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt index f43ddb8888..14ab9dc76f 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt @@ -21,15 +21,19 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.parentFragmentViewModel 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.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding import im.vector.app.features.settings.VectorLocale import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample +import reactivecircus.flowbinding.android.widget.editorActionEvents +import reactivecircus.flowbinding.android.widget.textChanges import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -53,22 +57,21 @@ class BootstrapEnterPassphraseFragment @Inject constructor() : views.ssssPassphraseEnterEdittext.setText(it.passphrase ?: "") } views.ssssPassphraseEnterEdittext.editorActionEvents() - .throttleFirst(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { + .sample(300) + .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() } } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) views.ssssPassphraseEnterEdittext.textChanges() - .subscribe { + .onEach { // ssss_passphrase_enter_til.error = null - sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: "")) + sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it.toString())) // ssss_passphrase_submit.isEnabled = it.isNotBlank() } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) sharedViewModel.observeViewEvents { // when (it) { diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt index b40a194a15..1224f4330e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt @@ -27,10 +27,9 @@ import android.view.ViewGroup import android.view.inputmethod.EditorInfo import androidx.core.text.toSpannable import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.parentFragmentViewModel 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.core.extensions.hideKeyboard import im.vector.app.core.extensions.registerStartForActivityResult @@ -40,8 +39,13 @@ import im.vector.app.core.utils.colorizeMatchingText import im.vector.app.core.utils.startImportTextFromFileIntent import im.vector.app.databinding.FragmentBootstrapMigrateBackupBinding import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey +import reactivecircus.flowbinding.android.widget.editorActionEvents +import reactivecircus.flowbinding.android.widget.textChanges import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -63,22 +67,21 @@ class BootstrapMigrateBackupFragment @Inject constructor( views.bootstrapMigrateEditText.setText(it.passphrase ?: "") } views.bootstrapMigrateEditText.editorActionEvents() - .throttleFirst(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { + .sample(300) + .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() } } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) views.bootstrapMigrateEditText.textChanges() .skipInitialValue() - .subscribe { + .onEach { views.bootstrapRecoveryKeyEnterTil.error = null // sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: "")) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) // sharedViewModel.observeViewEvents {} views.bootstrapMigrateContinueButton.debouncedClicks { submit() } diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt index 9af51f67b3..dd0bd174af 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolEditFragment.kt @@ -20,12 +20,15 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState -import com.jakewharton.rxbinding3.widget.textChanges import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.platform.VectorBaseFragment 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 class RoomDevToolEditFragment @Inject constructor() : @@ -44,10 +47,10 @@ class RoomDevToolEditFragment @Inject constructor() : } views.editText.textChanges() .skipInitialValue() - .subscribe { + .onEach { sharedViewModel.handle(RoomDevToolAction.UpdateContentText(it.toString())) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } override fun onResume() { diff --git a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt index 15e4e65d3b..fcea2e92b1 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerFragment.kt @@ -24,10 +24,10 @@ import android.view.inputmethod.EditorInfo import androidx.appcompat.app.AppCompatActivity import androidx.core.text.toSpannable import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.jakewharton.rxbinding3.widget.textChanges import im.vector.app.R import im.vector.app.core.extensions.exhaustive 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.databinding.FragmentSetIdentityServerBinding 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 reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject class SetIdentityServerFragment @Inject constructor( @@ -90,11 +93,11 @@ class SetIdentityServerFragment @Inject constructor( views.identityServerSetDefaultAlternativeTextInput .textChanges() - .subscribe { + .onEach { views.identityServerSetDefaultAlternativeTil.error = null views.identityServerSetDefaultAlternativeSubmit.isEnabled = it.isNotEmpty() } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) views.identityServerSetDefaultSubmit.debouncedClicks { viewModel.handle(SetIdentityServerAction.UseDefaultIdentityServer) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index a7d361c757..778b16e813 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -36,7 +36,6 @@ import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.showInvites import im.vector.app.features.settings.VectorDataStore import im.vector.app.features.ui.UiStateRepository -import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 9b103cebb5..b78db9dfea 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -66,8 +66,6 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState 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 im.vector.app.R import im.vector.app.core.dialogs.ConfirmationDialogBuilder @@ -185,7 +183,9 @@ import im.vector.app.features.widgets.WidgetArgs import im.vector.app.features.widgets.WidgetKind import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import nl.dionsegijn.konfetti.models.Shape @@ -217,6 +217,8 @@ import org.matrix.android.sdk.api.util.MimeTypes 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.WithHeldCode +import reactivecircus.flowbinding.android.view.focusChanges +import reactivecircus.flowbinding.android.widget.textChanges import timber.log.Timber import java.net.URL import java.util.UUID @@ -1349,19 +1351,19 @@ class RoomDetailFragment @Inject constructor( private fun observerUserTyping() { views.composerLayout.views.composerEditText.textChanges() .skipInitialValue() - .debounce(300, TimeUnit.MILLISECONDS) + .sample(300) .map { it.isNotEmpty() } - .subscribe { + .onEach { Timber.d("Typing: User is typing: $it") textComposerViewModel.handle(TextComposerAction.UserIsTyping(it)) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) views.composerLayout.views.composerEditText.focusChanges() - .subscribe { + .onEach { roomDetailViewModel.handle(RoomDetailAction.ComposerFocusChange(it)) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun sendUri(uri: Uri): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt index 324164bf58..686d767850 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt @@ -31,10 +31,8 @@ import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs -import io.reactivex.Observable import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.flow.FlowRoom import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListDisplayModeFilter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListDisplayModeFilter.kt index b8cdac19cf..f0bd070491 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListDisplayModeFilter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListDisplayModeFilter.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.list import im.vector.app.features.home.RoomListDisplayMode -import io.reactivex.functions.Predicate +import androidx.core.util.Predicate import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListNameFilter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListNameFilter.kt index 274cf7c869..edd4c86060 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListNameFilter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListNameFilter.kt @@ -16,7 +16,7 @@ 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 javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index 1e806bb2d8..bde324e57b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -30,12 +30,8 @@ import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.showInvites import im.vector.app.space -import io.reactivex.Observable -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest diff --git a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt index a1cc6d6d5e..73876bf6e9 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt @@ -18,7 +18,6 @@ package im.vector.app.features.invite import im.vector.app.ActiveSessionDataSource import im.vector.app.features.session.coroutineScope -import io.reactivex.disposables.Disposable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt index c613cf93d5..c9ede27c08 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt @@ -25,21 +25,24 @@ import android.view.inputmethod.EditorInfo import androidx.autofill.HintConstants import androidx.core.text.isDigitsOnly import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success -import com.jakewharton.rxbinding3.widget.textChanges import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginBinding -import io.reactivex.Observable -import io.reactivex.rxkotlin.subscribeBy +import kotlinx.coroutines.flow.combine +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.MatrixError import org.matrix.android.sdk.api.failure.isInvalidPassword +import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject /** @@ -224,20 +227,18 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment - isLoginNotEmpty && isPasswordNotEmpty - } - ) - .subscribeBy { + combine( + views.loginField.textChanges().map { it.trim().isNotEmpty() }, + views.passwordField.textChanges().map { it.isNotEmpty() } + ) { isLoginNotEmpty, isPasswordNotEmpty -> + isLoginNotEmpty && isPasswordNotEmpty + } + .onEach { views.loginFieldTil.error = null views.passwordFieldTil.error = null views.loginSubmit.isEnabled = it } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun forgetPasswordClicked() { diff --git a/vector/src/main/java/im/vector/app/features/login/LoginGenericTextInputFormFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginGenericTextInputFormFragment.kt index 925a5c05ab..150cb700cb 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginGenericTextInputFormFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginGenericTextInputFormFragment.kt @@ -25,19 +25,22 @@ import android.view.View import android.view.ViewGroup import androidx.autofill.HintConstants import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.args import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.PhoneNumberUtil -import com.jakewharton.rxbinding3.widget.textChanges import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.setTextOrHide import im.vector.app.databinding.FragmentLoginGenericTextInputFormBinding +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.is401 +import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject enum class TextInputFormFragmentMode { @@ -93,10 +96,10 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra private fun setupTil() { views.loginGenericTextInputFormTextInput.textChanges() - .subscribe { + .onEach { views.loginGenericTextInputFormTil.error = null } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun setupUi() { @@ -195,10 +198,10 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra private fun setupSubmitButton() { views.loginGenericTextInputFormSubmit.isEnabled = false views.loginGenericTextInputFormTextInput.textChanges() - .subscribe { + .onEach { views.loginGenericTextInputFormSubmit.isEnabled = isInputValid(it) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun isInputValid(input: CharSequence): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt index cc113b1eed..a06a569f44 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt @@ -20,19 +20,22 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.jakewharton.rxbinding3.widget.textChanges import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginResetPasswordBinding -import io.reactivex.Observable -import io.reactivex.rxkotlin.subscribeBy +import kotlinx.coroutines.flow.combine +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 /** @@ -59,21 +62,18 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment - isEmail && isPasswordNotEmpty - } - ) - .subscribeBy { + combine( + views.resetPasswordEmail.textChanges().map { it.isEmail() }, + views.passwordField.textChanges().map { it.isNotEmpty() } + ) { isEmail, isPasswordNotEmpty -> + isEmail && isPasswordNotEmpty + } + .onEach { views.resetPasswordEmailTil.error = null views.passwordFieldTil.error = null views.resetPasswordSubmit.isEnabled = it } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun submit() { diff --git a/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt index d0b4d65b19..cafffc9405 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt @@ -25,15 +25,18 @@ import android.view.inputmethod.EditorInfo import android.widget.ArrayAdapter import androidx.core.view.isInvisible import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.google.android.material.textfield.TextInputLayout -import com.jakewharton.rxbinding3.widget.textChanges import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.utils.ensureProtocol import im.vector.app.core.utils.openUrlInChromeCustomTab 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 reactivecircus.flowbinding.android.widget.textChanges import java.net.UnknownHostException import javax.inject.Inject @@ -61,11 +64,11 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment if (actionId == EditorInfo.IME_ACTION_DONE) { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt index 71f1d10137..5c9cefd2db 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninPassword2.kt @@ -24,17 +24,20 @@ import android.view.ViewGroup import android.view.inputmethod.EditorInfo import androidx.autofill.HintConstants import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Fail -import com.jakewharton.rxbinding3.widget.textChanges import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword import im.vector.app.databinding.FragmentLoginSigninPassword2Binding 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.failure.Failure import org.matrix.android.sdk.api.failure.isInvalidPassword +import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject import javax.net.ssl.HttpsURLConnection @@ -121,11 +124,11 @@ class LoginFragmentSigninPassword2 @Inject constructor( views.passwordField .textChanges() .map { it.isNotEmpty() } - .subscribeBy { + .onEach { views.passwordFieldTil.error = null views.loginSubmit.isEnabled = it } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun forgetPasswordClicked() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt index 95862a4e90..b90887dba1 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSigninUsername2.kt @@ -22,13 +22,16 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.autofill.HintConstants -import com.jakewharton.rxbinding3.widget.textChanges +import androidx.lifecycle.lifecycleScope import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard 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.MatrixError +import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject /** @@ -83,11 +86,11 @@ class LoginFragmentSigninUsername2 @Inject constructor() : AbstractLoginFragment views.loginSubmit.setOnClickListener { submit() } views.loginField.textChanges() .map { it.trim().isNotEmpty() } - .subscribeBy { + .onEach { views.loginFieldTil.error = null views.loginSubmit.isEnabled = it } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } override fun resetViewModel() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupPassword2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupPassword2.kt index 28a87a0e8b..806ff0524b 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupPassword2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupPassword2.kt @@ -23,12 +23,14 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo import androidx.autofill.HintConstants -import com.jakewharton.rxbinding3.widget.textChanges +import androidx.lifecycle.lifecycleScope import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hidePassword 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 /** @@ -87,11 +89,11 @@ class LoginFragmentSignupPassword2 @Inject constructor() : AbstractLoginFragment private fun setupSubmitButton() { views.loginSubmit.setOnClickListener { submit() } views.passwordField.textChanges() - .subscribeBy { password -> + .onEach { password -> views.passwordFieldTil.error = null views.loginSubmit.isEnabled = password.isNotEmpty() } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } override fun resetViewModel() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt index ff6a218796..51044ac153 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt @@ -24,14 +24,17 @@ import android.view.View import android.view.ViewGroup import androidx.autofill.HintConstants import androidx.core.view.isVisible -import com.jakewharton.rxbinding3.widget.textChanges +import androidx.lifecycle.lifecycleScope import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.toReducedUrl import im.vector.app.databinding.FragmentLoginSignupUsername2Binding import im.vector.app.features.login.LoginMode 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 /** @@ -111,12 +114,12 @@ class LoginFragmentSignupUsername2 @Inject constructor() : AbstractSSOLoginFragm views.loginSubmit.setOnClickListener { submit() } views.loginField.textChanges() .map { it.trim() } - .subscribeBy { text -> + .onEach { text -> val isNotEmpty = text.isNotEmpty() views.loginFieldTil.error = null views.loginSubmit.isEnabled = isNotEmpty } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } override fun resetViewModel() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt index a889c870a0..48792da007 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt @@ -24,7 +24,7 @@ import android.view.ViewGroup import android.view.inputmethod.EditorInfo import androidx.autofill.HintConstants import androidx.core.view.isVisible -import com.jakewharton.rxbinding3.widget.textChanges +import androidx.lifecycle.lifecycleScope import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard 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.features.login.LoginMode import im.vector.app.features.login.SocialLoginButtonsView -import io.reactivex.Observable -import io.reactivex.rxkotlin.subscribeBy +import kotlinx.coroutines.flow.combine +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.MatrixError import org.matrix.android.sdk.api.failure.isInvalidPassword +import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject /** @@ -136,20 +139,18 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2 - isLoginNotEmpty && isPasswordNotEmpty - } - ) - .subscribeBy { + combine( + views.loginField.textChanges().map { it.trim().isNotEmpty() }, + views.passwordField.textChanges().map { it.isNotEmpty() } + ) { isLoginNotEmpty, isPasswordNotEmpty -> + isLoginNotEmpty && isPasswordNotEmpty + } + .onEach { views.loginFieldTil.error = null views.passwordFieldTil.error = null views.loginSubmit.isEnabled = it } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun forgetPasswordClicked() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt index a87dd6ae40..76af86fda8 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginGenericTextInputFormFragment2.kt @@ -24,10 +24,10 @@ import android.view.View import android.view.ViewGroup import androidx.autofill.HintConstants import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.args import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.PhoneNumberUtil -import com.jakewharton.rxbinding3.widget.textChanges import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard 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.features.login.LoginGenericTextInputFormFragmentArgument 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.failure.Failure import org.matrix.android.sdk.api.failure.is401 +import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject /** @@ -82,10 +85,10 @@ class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFr private fun setupTil() { views.loginGenericTextInputFormTextInput.textChanges() - .subscribe { + .onEach { views.loginGenericTextInputFormTil.error = null } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun setupUi() { @@ -189,11 +192,11 @@ class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFr private fun setupSubmitButton() { views.loginGenericTextInputFormSubmit.isEnabled = false views.loginGenericTextInputFormTextInput.textChanges() - .subscribe { text -> + .onEach { text -> views.loginGenericTextInputFormSubmit.isEnabled = isInputValid(text) - text?.let { updateSubmitButtons(it) } + updateSubmitButtons(text) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun updateSubmitButtons(text: CharSequence) { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt index 6d6ddbc470..c00ed7275c 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginResetPasswordFragment2.kt @@ -23,8 +23,8 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo import androidx.autofill.HintConstants +import androidx.lifecycle.lifecycleScope import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.jakewharton.rxbinding3.widget.textChanges import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard 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.utils.autoResetTextInputLayoutErrors import im.vector.app.databinding.FragmentLoginResetPassword2Binding -import io.reactivex.Observable -import io.reactivex.rxkotlin.subscribeBy +import kotlinx.coroutines.flow.combine +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 /** @@ -78,19 +81,16 @@ class LoginResetPasswordFragment2 @Inject constructor() : AbstractLoginFragment2 private fun setupSubmitButton() { views.resetPasswordSubmit.setOnClickListener { submit() } - - Observable - .combineLatest( - views.resetPasswordEmail.textChanges().map { it.isEmail() }, - views.passwordField.textChanges().map { it.isNotEmpty() }, - { isEmail, isPasswordNotEmpty -> - isEmail && isPasswordNotEmpty - } - ) - .subscribeBy { + combine( + views.resetPasswordEmail.textChanges().map { it.isEmail() }, + views.passwordField.textChanges().map { it.isNotEmpty() } + ) { isEmail, isPasswordNotEmpty -> + isEmail && isPasswordNotEmpty + } + .onEach { views.resetPasswordSubmit.isEnabled = it } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun submit() { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt index 6a67f0513c..0732d176ac 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginServerUrlFormFragment2.kt @@ -24,15 +24,18 @@ import android.view.ViewGroup import android.view.inputmethod.EditorInfo import android.widget.ArrayAdapter import androidx.core.view.isInvisible +import androidx.lifecycle.lifecycleScope import com.google.android.material.textfield.TextInputLayout -import com.jakewharton.rxbinding3.widget.textChanges import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.utils.ensureProtocol 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.MatrixError +import reactivecircus.flowbinding.android.widget.textChanges import java.net.UnknownHostException import javax.inject.Inject import javax.net.ssl.HttpsURLConnection @@ -60,11 +63,11 @@ class LoginServerUrlFormFragment2 @Inject constructor() : AbstractLoginFragment2 private fun setupHomeServerField() { views.loginServerUrlFormHomeServerUrl.textChanges() - .subscribe { + .onEach { views.loginServerUrlFormHomeServerUrlTil.error = null views.loginServerUrlFormSubmit.isEnabled = it.isNotBlank() } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) views.loginServerUrlFormHomeServerUrl.setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_DONE) { diff --git a/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt b/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt index 5675af6960..2f81ee02e3 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt @@ -28,7 +28,6 @@ import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.viewModel import com.google.android.material.tabs.TabLayout -import com.jakewharton.rxbinding3.widget.queryTextChanges import dagger.hilt.android.AndroidEntryPoint import im.vector.app.EmojiCompatFontProvider import im.vector.app.R @@ -37,7 +36,11 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityEmojiReactionPickerBinding 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.flow.sample import kotlinx.coroutines.launch +import reactivecircus.flowbinding.android.widget.queryTextChanges import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -167,13 +170,11 @@ class EmojiReactionPickerActivity : VectorBaseActivity Timber.e(err) } - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { query -> + .sample(600) + .onEach { query -> onQueryText(query.toString()) } - .disposeOnDestroy() + .launchIn(lifecycleScope) } searchItem.expandActionView() return true diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt index b61583df55..80223268b5 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt @@ -25,7 +25,6 @@ import android.view.ViewGroup import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState -import com.jakewharton.rxbinding3.appcompat.queryTextChanges import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -37,10 +36,13 @@ import im.vector.app.core.utils.toast import im.vector.app.databinding.FragmentPublicRoomsBinding import im.vector.app.features.permalink.NavigationInterceptor 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 org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom +import reactivecircus.flowbinding.appcompat.queryTextChanges import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -79,11 +81,11 @@ class PublicRoomsFragment @Inject constructor( setupRecyclerView() views.publicRoomsFilter.queryTextChanges() - .debounce(500, TimeUnit.MILLISECONDS) - .subscribeBy { + .debounce(500) + .onEach { viewModel.handle(RoomDirectoryAction.FilterWith(it.toString())) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) views.publicRoomsCreateNewRoom.debouncedClicks { sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoom) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt index 449b8663d6..eef8396c1a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt @@ -16,7 +16,7 @@ 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.session.room.model.RoomMemberSummary import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt index bb8db019c3..c826fe5e64 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt @@ -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.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedActions 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.RoomJoinRuleChooseRestrictedViewModel import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedFragment.kt index 6d5d9c1f30..bb038fe887 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedFragment.kt @@ -14,26 +14,26 @@ * 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.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState -import com.jakewharton.rxbinding3.appcompat.queryTextChanges import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSpaceRestrictedSelectBinding import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.roomprofile.settings.joinrule.advanced.ChooseRestrictedController -import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedActions -import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel -import io.reactivex.rxkotlin.subscribeBy +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.util.MatrixItem +import reactivecircus.flowbinding.appcompat.queryTextChanges import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -54,11 +54,11 @@ class RoomJoinRuleChooseRestrictedFragment @Inject constructor( controller.listener = this views.recyclerView.configureWith(controller) views.roomsFilter.queryTextChanges() - .debounce(500, TimeUnit.MILLISECONDS) - .subscribeBy { + .debounce(500) + .onEach { viewModel.handle(RoomJoinRuleChooseRestrictedActions.FilterWith(it.toString())) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) views.okButton.debouncedClicks { parentFragmentManager.popBackStack() diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt index bffabf2e93..0a12a86ff0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt @@ -27,8 +27,6 @@ import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.platform.VectorBaseActivity 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 timber.log.Timber @@ -67,31 +65,10 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat() { 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 var titleRes: Int - /* ========================================================================================== - * Disposable - * ========================================================================================== */ - - private val uiDisposables = CompositeDisposable() - - protected fun Disposable.disposeOnDestroyView() { - uiDisposables.add(this) - } - /* ========================================================================================== * Protected * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index ca96d6a6bc..4e6859f5de 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -58,7 +58,6 @@ import im.vector.app.features.pin.PinMode import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.isE2EByDefault 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 @@ -85,7 +84,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( override var titleRes = R.string.settings_security_and_privacy override val preferenceXmlRes = R.xml.vector_settings_security_privacy - private var disposables = mutableListOf() // cryptography private val mCryptographyCategory by lazy { @@ -172,14 +170,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( // findPreference(VectorPreferences.SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY) // } - override fun onPause() { - super.onPause() - disposables.forEach { - it.dispose() - } - disposables.clear() - } - private fun refresh4SSection(info: SecretsSynchronisationInfo) { // it's a lot of if / else if / else // But it's not yet clear how to manage all cases diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt index 154518778d..cf0bb8146e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt @@ -34,7 +34,6 @@ 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.login.ReAuthHelper -import io.reactivex.subjects.PublishSubject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged diff --git a/vector/src/main/java/im/vector/app/features/spaces/LeaveSpaceBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/LeaveSpaceBottomSheet.kt index fb3fceaa02..d8ee03a8f5 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/LeaveSpaceBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/LeaveSpaceBottomSheet.kt @@ -26,12 +26,12 @@ import android.view.ViewGroup import androidx.core.text.toSpannable import androidx.core.view.isInvisible import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.args import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState -import com.jakewharton.rxbinding3.widget.checkedChanges import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.error.ErrorFormatter @@ -44,9 +44,12 @@ import im.vector.app.databinding.BottomSheetLeaveSpaceBinding import im.vector.app.features.displayname.getBestName 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 me.gujun.android.span.span import org.matrix.android.sdk.api.util.toMatrixItem +import reactivecircus.flowbinding.android.widget.checkedChanges import javax.inject.Inject @AndroidEntryPoint @@ -82,8 +85,7 @@ class LeaveSpaceBottomSheet : VectorBaseBottomSheetDialogFragment { settingsViewModel.handle(SpaceLeaveViewAction.SetAutoLeaveAll) @@ -100,7 +102,7 @@ class LeaveSpaceBottomSheet : VectorBaseBottomSheetDialogFragment when (actionState) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt index dad8ecfcca..8abc189def 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt @@ -20,13 +20,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState -import com.jakewharton.rxbinding3.appcompat.queryTextChanges import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -37,8 +37,11 @@ import im.vector.app.core.resources.DrawableProvider import im.vector.app.databinding.FragmentRecyclerviewWithSearchBinding import im.vector.app.features.roomprofile.members.RoomMemberListAction import im.vector.app.features.roomprofile.members.RoomMemberListViewModel -import io.reactivex.rxkotlin.subscribeBy +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary +import reactivecircus.flowbinding.appcompat.queryTextChanges import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -117,11 +120,11 @@ class SpacePeopleFragment @Inject constructor( private fun setupSearchView() { views.memberNameFilter.queryHint = getString(R.string.search_members_hint) views.memberNameFilter.queryTextChanges() - .debounce(100, TimeUnit.MILLISECONDS) - .subscribeBy { + .debounce(100) + .onEach { membersViewModel.handle(RoomMemberListAction.FilterMemberList(it.toString())) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun handleViewEvents(events: SpacePeopleViewEvents) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt index 7e08d7c924..8d3d623525 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt @@ -22,13 +22,13 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState -import com.jakewharton.rxbinding3.appcompat.navigationClicks import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -37,10 +37,12 @@ import im.vector.app.databinding.FragmentSpacePreviewBinding import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.spaces.SpacePreviewSharedAction import im.vector.app.features.spaces.SpacePreviewSharedActionViewModel -import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.util.MatrixItem -import java.util.concurrent.TimeUnit +import reactivecircus.flowbinding.appcompat.navigationClicks import javax.inject.Inject @Parcelize @@ -72,11 +74,11 @@ class SpacePreviewFragment @Inject constructor( handleViewEvents(it) } - views.roomPreviewNoPreviewToolbar.navigationClicks() - .throttleFirst(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { sharedActionViewModel.post(SpacePreviewSharedAction.DismissAction) } - .disposeOnDestroyView() + views.roomPreviewNoPreviewToolbar + .navigationClicks() + .sample(300) + .onEach { sharedActionViewModel.post(SpacePreviewSharedAction.DismissAction) } + .launchIn(viewLifecycleOwner.lifecycleScope) views.spacePreviewRecyclerView.configureWith(epoxyController) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt index aed134816a..edcd02f639 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt @@ -31,7 +31,6 @@ import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.chip.Chip -import com.jakewharton.rxbinding3.widget.textChanges import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -45,8 +44,13 @@ import im.vector.app.databinding.FragmentUserListBinding import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel import im.vector.app.features.navigation.SettingsActivityPayload import im.vector.app.features.settings.VectorSettingsActivity +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.startWith import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.user.model.User +import reactivecircus.flowbinding.android.widget.textChanges import javax.inject.Inject class UserListFragment @Inject constructor( @@ -133,8 +137,8 @@ class UserListFragment @Inject constructor( private fun setupSearchView() { views.userListSearch .textChanges() - .startWith(views.userListSearch.text) - .subscribe { text -> + .onStart { emit(views.userListSearch.text)} + .onEach { text -> val searchValue = text.trim() val action = if (searchValue.isBlank()) { UserListAction.ClearSearchUsers @@ -143,7 +147,7 @@ class UserListFragment @Inject constructor( } viewModel.handle(action) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) views.userListSearch.setupAsSearch() views.userListSearch.requestFocus()