From c936954119b920eb14fc6a9734599f0ed54f2700 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Oct 2021 14:24:23 +0200 Subject: [PATCH 01/12] Flow migration: start replacing Rx by Flow --- vector/build.gradle | 3 - .../java/im/vector/app/AppStateHandler.kt | 20 +- .../app/core/platform/VectorBaseActivity.kt | 12 +- .../VectorBaseBottomSheetDialogFragment.kt | 12 +- .../app/core/platform/VectorBaseFragment.kt | 14 +- .../app/core/platform/VectorViewModel.kt | 40 +--- .../im/vector/app/core/utils/DataSource.kt | 26 +-- .../features/call/CallControlsBottomSheet.kt | 2 +- .../app/features/call/VectorCallActivity.kt | 14 +- .../call/conference/VectorJitsiActivity.kt | 2 +- .../createdirect/CreateDirectRoomActivity.kt | 9 +- .../quads/SharedSecureStorageActivity.kt | 2 +- .../SharedSecuredStorageResetAllFragment.kt | 2 +- .../features/devtools/RoomDevToolActivity.kt | 2 +- .../vector/app/features/home/HomeActivity.kt | 12 +- .../app/features/home/HomeDetailFragment.kt | 2 +- .../app/features/home/HomeDetailViewModel.kt | 30 +-- .../home/PromoteRestrictedViewModel.kt | 1 + .../home/UnreadMessagesSharedViewModel.kt | 172 +++++++++--------- .../home/room/detail/RoomDetailActivity.kt | 9 +- .../home/room/detail/RoomDetailFragment.kt | 8 +- .../home/room/detail/RoomDetailViewModel.kt | 19 +- .../detail/composer/TextComposerViewModel.kt | 2 +- .../reactions/ViewReactionsViewModel.kt | 52 +++--- .../home/room/list/RoomListFragment.kt | 9 +- .../home/room/list/RoomListSectionBuilder.kt | 2 - .../room/list/RoomListSectionBuilderGroup.kt | 32 ++-- .../room/list/RoomListSectionBuilderSpace.kt | 54 +++--- .../home/room/list/RoomListViewModel.kt | 5 +- .../invite/InviteUsersToRoomActivity.kt | 9 +- .../app/features/invite/InvitesAcceptor.kt | 12 +- .../app/features/login/LoginActivity.kt | 7 +- .../app/features/login2/LoginActivity2.kt | 13 +- .../powerlevel/PowerLevelsFlowFactory.kt | 2 +- .../room/RequireActiveMembershipViewModel.kt | 2 +- .../roomdirectory/RoomDirectoryActivity.kt | 9 +- .../createroom/CreateRoomActivity.kt | 9 +- .../createroom/CreateRoomFragment.kt | 9 +- .../roomprofile/RoomProfileActivity.kt | 9 +- .../roomprofile/RoomProfileFragment.kt | 9 +- .../roomprofile/alias/RoomAliasFragment.kt | 9 +- .../members/RoomMemberListViewModel.kt | 1 - .../settings/RoomSettingsFragment.kt | 15 +- .../VectorSettingsSecurityPrivacyFragment.kt | 4 +- .../settings/devices/DevicesViewModel.kt | 12 +- .../signout/soft/SoftLogoutActivity.kt | 2 +- .../signout/soft/SoftLogoutActivity2.kt | 2 +- .../signout/soft/SoftLogoutFragment.kt | 2 +- .../features/spaces/SpaceCreationActivity.kt | 2 +- .../app/features/spaces/SpaceListViewModel.kt | 17 +- .../features/spaces/SpacePreviewActivity.kt | 9 +- .../CreateSpaceAdd3pidInvitesFragment.kt | 2 +- .../create/CreateSpaceDefaultRoomsFragment.kt | 2 +- .../create/CreateSpaceDetailsFragment.kt | 2 +- .../leave/SpaceLeaveAdvancedActivity.kt | 2 +- .../spaces/manage/SpaceManageActivity.kt | 9 +- .../spaces/manage/SpaceSettingsFragment.kt | 9 +- .../spaces/people/SpacePeopleActivity.kt | 9 +- .../spaces/people/SpacePeopleFragment.kt | 2 +- .../userdirectory/UserListViewModel.kt | 4 +- .../java/im/vector/app/test/Extensions.kt | 8 +- 61 files changed, 408 insertions(+), 375 deletions(-) diff --git a/vector/build.gradle b/vector/build.gradle index 5f032e55c2..35d1acfe8b 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -388,9 +388,6 @@ dependencies { kapt libs.airbnb.epoxyProcessor implementation libs.airbnb.epoxyPaging implementation libs.airbnb.mavericks - //TODO: remove when entirely migrated to Flow - implementation libs.airbnb.mavericksRx - // Work implementation libs.androidx.work diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index 30078963f4..650047787e 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -25,12 +25,19 @@ 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 +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch 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 @@ -54,10 +61,10 @@ class AppStateHandler @Inject constructor( private val activeSessionHolder: ActiveSessionHolder ) : LifecycleObserver { - private val compositeDisposable = CompositeDisposable() + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private val selectedSpaceDataSource = BehaviorDataSource>(Option.empty()) - val selectedRoomGroupingObservable = selectedSpaceDataSource.observe() + val selectedRoomGroupingObservable = selectedSpaceDataSource.stream() fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? { // XXX we should somehow make it live :/ just a work around @@ -105,9 +112,9 @@ class AppStateHandler @Inject constructor( } private fun observeActiveSession() { - sessionDataSource.observe() + sessionDataSource.stream() .distinctUntilChanged() - .subscribe { + .onEach { // sessionDataSource could already return a session while activeSession holder still returns null it.orNull()?.let { session -> if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) { @@ -116,9 +123,8 @@ class AppStateHandler @Inject constructor( setCurrentGroup(uiStateRepository.getSelectedGroup(session.sessionId), session) } } - }.also { - compositeDisposable.add(it) } + .launchIn(coroutineScope) } fun safeActiveSpaceId(): String? { @@ -136,7 +142,7 @@ class AppStateHandler @Inject constructor( @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun entersBackground() { - compositeDisposable.clear() + coroutineScope.coroutineContext.cancelChildren() val session = activeSessionHolder.getSafeActiveSession() ?: return when (val currentMethod = selectedSpaceDataSource.currentValue?.orNull() ?: RoomGroupingMethod.BySpace(null)) { is RoomGroupingMethod.BySpace -> { 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 4d06dbe6a2..a28d9fa355 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 @@ -39,6 +39,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentFactory import androidx.fragment.app.FragmentManager import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import com.bumptech.glide.util.Util @@ -80,6 +81,10 @@ 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 org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.GlobalError import timber.log.Timber @@ -104,13 +109,12 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { + .stream() + .onEach { hideWaitingView() observer(it) } - .disposeOnDestroy() + .launchIn(lifecycleScope) } /* ========================================================================================== 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 711b2b144b..04a34a8876 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 @@ -26,6 +26,7 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.annotation.CallSuper import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.MavericksView @@ -39,6 +40,10 @@ 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 timber.log.Timber import java.util.concurrent.TimeUnit @@ -193,11 +198,10 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { + .stream() + .onEach { observer(it) } - .disposeOnDestroyView() + .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 d3c66ec61d..7dce2bc954 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 @@ -29,6 +29,7 @@ import androidx.annotation.MainThread import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import com.bumptech.glide.util.Util.assertMainThread @@ -47,6 +48,12 @@ 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 timber.log.Timber import java.util.concurrent.TimeUnit @@ -237,13 +244,12 @@ abstract class VectorBaseFragment : Fragment(), MavericksView protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { + .stream() + .onEach { dismissLoadingDialog() observer(it) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } /* ========================================================================================== diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt index 6e7c24d4e9..c9d58f9545 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt @@ -16,53 +16,17 @@ package im.vector.app.core.platform -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.BaseMvRxViewModel -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.Success +import com.airbnb.mvrx.MavericksViewModel import im.vector.app.core.utils.DataSource import im.vector.app.core.utils.PublishDataSource -import io.reactivex.Observable -import io.reactivex.Single abstract class VectorViewModel(initialState: S) : - BaseMvRxViewModel(initialState) { - - interface Factory { - fun create(state: S): BaseMvRxViewModel - } + MavericksViewModel(initialState) { // Used to post transient events to the View protected val _viewEvents = PublishDataSource() val viewEvents: DataSource = _viewEvents - /** - * This method does the same thing as the execute function, but it doesn't subscribe to the stream - * so you can use this in a switchMap or a flatMap - */ - // False positive - @Suppress("USELESS_CAST", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER") - fun Single.toAsync(stateReducer: S.(Async) -> S): Single> { - setState { stateReducer(Loading()) } - return map { Success(it) as Async } - .onErrorReturn { Fail(it) } - .doOnSuccess { setState { stateReducer(it) } } - } - - /** - * This method does the same thing as the execute function, but it doesn't subscribe to the stream - * so you can use this in a switchMap or a flatMap - */ - // False positive - @Suppress("USELESS_CAST", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER") - fun Observable.toAsync(stateReducer: S.(Async) -> S): Observable> { - setState { stateReducer(Loading()) } - return map { Success(it) as Async } - .onErrorReturn { Fail(it) } - .doOnNext { setState { stateReducer(it) } } - } - abstract fun handle(action: VA) } 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 fc4ee330bb..6338768723 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 @@ -17,12 +17,12 @@ package im.vector.app.core.utils import com.jakewharton.rxrelay2.BehaviorRelay -import com.jakewharton.rxrelay2.PublishRelay -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow interface DataSource { - fun observe(): Observable + fun stream(): Flow } interface MutableDataSource : DataSource { @@ -34,17 +34,17 @@ interface MutableDataSource : DataSource { */ open class BehaviorDataSource(private val defaultValue: T? = null) : MutableDataSource { - private val behaviorRelay = createRelay() + private val mutableFlow = MutableSharedFlow(replay = 1) val currentValue: T? - get() = behaviorRelay.value + get() = mutableFlow.replayCache.firstOrNull() - override fun observe(): Observable { - return behaviorRelay.hide().observeOn(AndroidSchedulers.mainThread()) + override fun stream(): Flow { + return mutableFlow } override fun post(value: T) { - behaviorRelay.accept(value!!) + mutableFlow.tryEmit(value) } private fun createRelay(): BehaviorRelay { @@ -61,13 +61,13 @@ open class BehaviorDataSource(private val defaultValue: T? = null) : MutableD */ open class PublishDataSource : MutableDataSource { - private val publishRelay = PublishRelay.create() + private val mutableFlow = MutableSharedFlow(replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) - override fun observe(): Observable { - return publishRelay.hide().observeOn(AndroidSchedulers.mainThread()) + override fun stream(): Flow { + return mutableFlow } override fun post(value: T) { - publishRelay.accept(value!!) + mutableFlow.tryEmit(value) } } diff --git a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt index b4f49db781..e38b53c858 100644 --- a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt @@ -39,7 +39,7 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment(), CallContro setSupportActionBar(views.callToolbar) configureCallViews() - callViewModel.subscribe(this) { + callViewModel.onEach { renderState(it) } @@ -141,12 +146,11 @@ class VectorCallActivity : VectorBaseActivity(), CallContro } callViewModel.viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { + .stream() + .onEach { handleViewEvents(it) } - .disposeOnDestroy() + .launchIn(lifecycleScope) callViewModel.onEach(VectorCallViewState::callId, VectorCallViewState::isVideoCall) { _, isVideoCall -> if (isVideoCall) { diff --git a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt index 3fcefc9c8e..0fdfea8bff 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt @@ -68,7 +68,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMee override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - jitsiViewModel.subscribe(this) { + jitsiViewModel.onEach { renderState(it) } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index 28da72714a..3ff989da5a 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -22,6 +22,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.View +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -46,6 +47,8 @@ import im.vector.app.features.userdirectory.UserListFragment import im.vector.app.features.userdirectory.UserListFragmentArgs import im.vector.app.features.userdirectory.UserListSharedAction import im.vector.app.features.userdirectory.UserListSharedActionViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import java.net.HttpURLConnection @@ -64,8 +67,8 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java) sharedActionViewModel - .observe() - .subscribe { action -> + .stream() + .onEach { action -> when (action) { UserListSharedAction.Close -> finish() UserListSharedAction.GoBack -> onBackPressed() @@ -74,7 +77,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { UserListSharedAction.AddByQrCode -> openAddByQrCode() }.exhaustive } - .disposeOnDestroy() + .launchIn(lifecycleScope) if (isFirstCreation()) { addFragment( R.id.container, diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt index 61c8ab8f0a..bb854aca26 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt @@ -63,7 +63,7 @@ class SharedSecureStorageActivity : viewModel.observeViewEvents { observeViewEvents(it) } - viewModel.subscribe(this) { renderState(it) } + viewModel.onEach { renderState(it) } } override fun onDestroy() { diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt index 670e5c610a..200b2b73c2 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt @@ -55,7 +55,7 @@ class SharedSecuredStorageResetAllFragment @Inject constructor() : } } - sharedViewModel.subscribe(this) { state -> + sharedViewModel.onEach { state -> views.ssssResetOtherDevices.setTextOrHide( state.activeDeviceCount .takeIf { it > 0 } diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt index 772ef99931..2c7a15e6ad 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt @@ -66,7 +66,7 @@ class RoomDevToolActivity : SimpleFragmentActivity(), FragmentManager.OnBackStac override fun initUiAndData() { super.initUiAndData() - viewModel.subscribe(this) { + viewModel.onEach { renderState(it) } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index e8af044bbd..04ca25332f 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -73,6 +73,8 @@ import im.vector.app.features.spaces.share.ShareSpaceBottomSheet import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import im.vector.app.push.fcm.FcmHelper +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.initsync.SyncStatusService @@ -178,8 +180,8 @@ class HomeActivity : } sharedActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START) is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START) @@ -222,7 +224,7 @@ class HomeActivity : } }.exhaustive } - .disposeOnDestroy() + .launchIn(lifecycleScope) val args = intent.getParcelableExtra(Mavericks.KEY_ARG) @@ -243,13 +245,13 @@ class HomeActivity : is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) }.exhaustive } - homeActivityViewModel.subscribe(this) { renderState(it) } + homeActivityViewModel.onEach { renderState(it) } shortcutsHandler.observeRoomsAndBuildShortcuts() .disposeOnDestroy() if (!vectorPreferences.didPromoteNewRestrictedFeature()) { - promoteRestrictedViewModel.subscribe(this) { + promoteRestrictedViewModel.onEach { if (it.activeSpaceSummary != null && !it.activeSpaceSummary.isPublic && it.activeSpaceSummary.otherMemberIds.isNotEmpty()) { // It's a private space with some members show this once diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index 80351a437e..55d8e2e09a 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -299,7 +299,7 @@ class HomeDetailFragment @Inject constructor( private fun setupKeysBackupBanner() { serverBackupStatusViewModel - .subscribe(this) { + .onEach { when (val banState = it.bannerState.invoke()) { is BannerState.Setup -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false) BannerState.BackingUp -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false) 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 73e50ad5f1..8bfc1a8db4 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 @@ -39,7 +39,13 @@ 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 import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample +import kotlinx.coroutines.flow.switchMap import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter @@ -66,7 +72,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho private val directRoomHelper: DirectRoomHelper, private val appStateHandler: AppStateHandler, private val autoAcceptInvites: AutoAcceptInvites) : - VectorViewModel(initialState), + VectorViewModel(initialState), CallProtocolsChecker.Listener { @AssistedFactory @@ -194,18 +200,15 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho private fun observeRoomGroupingMethod() { appStateHandler.selectedRoomGroupingObservable - .subscribe { - setState { - copy( - roomGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null) - ) - } + .setOnEach { + copy( + roomGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null) + ) } - .disposeOnClear() } private fun observeRoomSummaries() { - appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged().switchMap { + appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged().flatMapLatest { // we use it as a trigger to all changes in room, but do not really load // the actual models session.getPagedRoomSummariesLive( @@ -213,11 +216,10 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho memberships = Membership.activeMemberships() }, sortOrder = RoomSortOrder.NONE - ).asObservable() + ).asFlow() } - .observeOn(Schedulers.computation()) - .throttleFirst(300, TimeUnit.MILLISECONDS) - .subscribe { + .sample(300) + .onEach { when (val groupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) { is RoomGroupingMethod.ByLegacyGroup -> { // TODO!! @@ -274,6 +276,6 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho } } } - .disposeOnClear() + .launchIn(viewModelScope) } } diff --git a/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt index 218574c03e..77ee23f732 100644 --- a/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt @@ -29,6 +29,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.flow.distinctUntilChanged import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index 5bdbc95b48..3434f9dfb0 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home +import androidx.lifecycle.asFlow import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted @@ -30,8 +31,12 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences -import io.reactivex.Observable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.RoomSortOrder @@ -57,7 +62,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia private val vectorPreferences: VectorPreferences, appStateHandler: AppStateHandler, private val autoAcceptInvites: AutoAcceptInvites) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -75,8 +80,8 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia this.memberships = listOf(Membership.JOIN) this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) }, sortOrder = RoomSortOrder.NONE - ).asObservable() - .throttleFirst(300, TimeUnit.MILLISECONDS) + ).asFlow() + .sample(300) .execute { val counts = session.getNotificationCountForRooms( roomSummaryQueryParams { @@ -103,91 +108,92 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia ) } - Observable.combineLatest( + combine( appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged(), - appStateHandler.selectedRoomGroupingObservable.switchMap { + appStateHandler.selectedRoomGroupingObservable.flatMapLatest { session.getPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = Membership.activeMemberships() }, sortOrder = RoomSortOrder.NONE - ).asObservable() - .throttleFirst(300, TimeUnit.MILLISECONDS) - .observeOn(Schedulers.computation()) - }, - { groupingMethod, _ -> - when (groupingMethod.orNull()) { - is RoomGroupingMethod.ByLegacyGroup -> { - // currently not supported - CountInfo( - RoomAggregateNotificationCount(0, 0), - RoomAggregateNotificationCount(0, 0) - ) - } - is RoomGroupingMethod.BySpace -> { - val selectedSpace = appStateHandler.safeActiveSpaceId() + ).asFlow() + .sample(300) - val inviteCount = if (autoAcceptInvites.hideInvites) { - 0 - } else { - session.getRoomSummaries( - roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } - ).size - } - - val spaceInviteCount = if (autoAcceptInvites.hideInvites) { - 0 - } else { - session.getRoomSummaries( - spaceSummaryQueryParams { - this.memberships = listOf(Membership.INVITE) - } - ).size - } - - val totalCount = session.getNotificationCountForRooms( - roomSummaryQueryParams { - this.memberships = listOf(Membership.JOIN) - this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf { - !vectorPreferences.prefSpacesShowAllRoomInHome() - } ?: ActiveSpaceFilter.None - } - ) - - val counts = RoomAggregateNotificationCount( - totalCount.notificationCount + inviteCount, - totalCount.highlightCount + inviteCount - ) - val rootCounts = session.spaceService().getRootSpaceSummaries() - .filter { - // filter out current selection - it.roomId != selectedSpace - } - - CountInfo( - homeCount = counts, - otherCount = RoomAggregateNotificationCount( - notificationCount = rootCounts.fold(0, { acc, rs -> acc + rs.notificationCount }) + - (counts.notificationCount.takeIf { selectedSpace != null } ?: 0) + - spaceInviteCount, - highlightCount = rootCounts.fold(0, { acc, rs -> acc + rs.highlightCount }) + - (counts.highlightCount.takeIf { selectedSpace != null } ?: 0) + - spaceInviteCount - ) - ) - } - null -> { - CountInfo( - RoomAggregateNotificationCount(0, 0), - RoomAggregateNotificationCount(0, 0) - ) - } - } } - ).execute { - copy( - homeSpaceUnread = it.invoke()?.homeCount ?: RoomAggregateNotificationCount(0, 0), - otherSpacesUnread = it.invoke()?.otherCount ?: RoomAggregateNotificationCount(0, 0) - ) + ) { groupingMethod, _ -> + when (groupingMethod.orNull()) { + is RoomGroupingMethod.ByLegacyGroup -> { + // currently not supported + CountInfo( + RoomAggregateNotificationCount(0, 0), + RoomAggregateNotificationCount(0, 0) + ) + } + is RoomGroupingMethod.BySpace -> { + val selectedSpace = appStateHandler.safeActiveSpaceId() + + val inviteCount = if (autoAcceptInvites.hideInvites) { + 0 + } else { + session.getRoomSummaries( + roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } + ).size + } + + val spaceInviteCount = if (autoAcceptInvites.hideInvites) { + 0 + } else { + session.getRoomSummaries( + spaceSummaryQueryParams { + this.memberships = listOf(Membership.INVITE) + } + ).size + } + + val totalCount = session.getNotificationCountForRooms( + roomSummaryQueryParams { + this.memberships = listOf(Membership.JOIN) + this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf { + !vectorPreferences.prefSpacesShowAllRoomInHome() + } ?: ActiveSpaceFilter.None + } + ) + + val counts = RoomAggregateNotificationCount( + totalCount.notificationCount + inviteCount, + totalCount.highlightCount + inviteCount + ) + val rootCounts = session.spaceService().getRootSpaceSummaries() + .filter { + // filter out current selection + it.roomId != selectedSpace + } + + CountInfo( + homeCount = counts, + otherCount = RoomAggregateNotificationCount( + notificationCount = rootCounts.fold(0, { acc, rs -> acc + rs.notificationCount }) + + (counts.notificationCount.takeIf { selectedSpace != null } ?: 0) + + spaceInviteCount, + highlightCount = rootCounts.fold(0, { acc, rs -> acc + rs.highlightCount }) + + (counts.highlightCount.takeIf { selectedSpace != null } ?: 0) + + spaceInviteCount + ) + ) + } + null -> { + CountInfo( + RoomAggregateNotificationCount(0, 0), + RoomAggregateNotificationCount(0, 0) + ) + } + } } + .flowOn(Dispatchers.Default) + .execute { + copy( + homeSpaceUnread = it.invoke()?.homeCount ?: RoomAggregateNotificationCount(0, 0), + otherSpacesUnread = it.invoke()?.otherCount ?: RoomAggregateNotificationCount(0, 0) + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt index ba53f75eca..415ca7bc04 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt @@ -24,6 +24,7 @@ import androidx.core.view.GravityCompat import androidx.drawerlayout.widget.DrawerLayout import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.google.android.material.appbar.MaterialToolbar @@ -40,6 +41,8 @@ import im.vector.app.features.navigation.Navigator import im.vector.app.features.room.RequireActiveMembershipAction import im.vector.app.features.room.RequireActiveMembershipViewEvents import im.vector.app.features.room.RequireActiveMembershipViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach @AndroidEntryPoint class RoomDetailActivity : @@ -97,13 +100,13 @@ class RoomDetailActivity : sharedActionViewModel = viewModelProvider.get(RoomDetailSharedActionViewModel::class.java) sharedActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { is RoomDetailSharedAction.SwitchToRoom -> switchToRoom(sharedAction) } } - .disposeOnDestroy() + .launchIn(lifecycleScope) requireActiveMembershipViewModel.observeViewEvents { when (it) { 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 d20c9796d2..9b103cebb5 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 @@ -184,6 +184,8 @@ import im.vector.app.features.widgets.WidgetActivity 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.onEach import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import nl.dionsegijn.konfetti.models.Shape @@ -365,11 +367,11 @@ class RoomDetailFragment @Inject constructor( } sharedActionViewModel - .observe() - .subscribe { + .stream() + .onEach { handleActions(it) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) knownCallsViewModel .liveKnownCalls diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 03bde7d4cc..ee929243b5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -27,7 +27,6 @@ import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext -import com.jakewharton.rxrelay2.BehaviorRelay import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -37,6 +36,7 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.BehaviorDataSource import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.call.conference.ConferenceEvent import im.vector.app.features.call.conference.JitsiActiveConferenceHolder @@ -56,7 +56,6 @@ import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorDataStore import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.voice.VoicePlayerHelper -import io.reactivex.rxkotlin.subscribeBy import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collect @@ -123,8 +122,8 @@ class RoomDetailViewModel @AssistedInject constructor( private val room = session.getRoom(initialState.roomId)!! private val eventId = initialState.eventId - private val invisibleEventsObservable = BehaviorRelay.create() - private val visibleEventsObservable = BehaviorRelay.create() + private val invisibleEventsSource = BehaviorDataSource() + private val visibleEventsSource = BehaviorDataSource() private var timelineEvents = MutableSharedFlow>(0) val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId) @@ -562,7 +561,7 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleEventInvisible(action: RoomDetailAction.TimelineEventTurnsInvisible) { - invisibleEventsObservable.accept(action) + invisibleEventsSource.post(action) } fun getMember(userId: String): RoomMemberSummary? { @@ -711,12 +710,12 @@ class RoomDetailViewModel @AssistedInject constructor( private fun handleEventVisible(action: RoomDetailAction.TimelineEventTurnsVisible) { viewModelScope.launch(Dispatchers.Default) { if (action.event.root.sendState.isSent()) { // ignore pending/local events - visibleEventsObservable.accept(action) + visibleEventsSource.post(action) } // We need to update this with the related m.replace also (to move read receipt) action.event.annotations?.editSummary?.sourceEvents?.forEach { room.getTimeLineEvent(it)?.let { event -> - visibleEventsObservable.accept(RoomDetailAction.TimelineEventTurnsVisible(event)) + visibleEventsSource.post(RoomDetailAction.TimelineEventTurnsVisible(event)) } } @@ -864,7 +863,9 @@ class RoomDetailViewModel @AssistedInject constructor( private fun observeEventDisplayedActions() { // We are buffering scroll events for one second // and keep the most recent one to set the read receipt on. - visibleEventsObservable + /* + visibleEventsSource + .stream() .buffer(1, TimeUnit.SECONDS) .filter { it.isNotEmpty() } .subscribeBy(onNext = { actions -> @@ -884,6 +885,8 @@ class RoomDetailViewModel @AssistedInject constructor( } }) .disposeOnClear() + + */ } private fun handleMarkAllAsRead() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index e80f25de2f..3d7c4c71f9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -104,7 +104,7 @@ class TextComposerViewModel @AssistedInject constructor( } private fun subscribeToStateInternal() { - selectSubscribe(TextComposerViewState::sendMode, TextComposerViewState::canSendMessage, TextComposerViewState::isVoiceRecording) { _, _, _ -> + onEach(TextComposerViewState::sendMode, TextComposerViewState::canSendMessage, TextComposerViewState::isVoiceRecording) { _, _, _ -> updateIsSendButtonVisibility(false) } } 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 1f4d67db03..324164bf58 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 @@ -32,17 +32,17 @@ 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 io.reactivex.Single +import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary -import org.matrix.android.sdk.rx.RxRoom -import org.matrix.android.sdk.rx.unwrap +import org.matrix.android.sdk.flow.FlowRoom +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap data class DisplayReactionsViewState( val eventId: String, val roomId: String, val mapReactionKeyToMemberList: Async> = Uninitialized) : - MavericksState { + MavericksState { constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId) } @@ -81,39 +81,31 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted } private fun observeEventAnnotationSummaries() { - RxRoom(room) + room.flow() .liveAnnotationSummary(eventId) .unwrap() - .flatMapSingle { summaries -> - Observable - .fromIterable(summaries.reactionsSummary) - // .filter { reactionAggregatedSummary -> isSingleEmoji(reactionAggregatedSummary.key) } - .toReactionInfoList() + .map { annotationsSummary -> + annotationsSummary.reactionsSummary + .flatMap { reactionsSummary -> + reactionsSummary.sourceEvents.map { + val event = room.getTimeLineEvent(it) + ?: throw RuntimeException("Your eventId is not valid") + ReactionInfo( + event.root.eventId!!, + reactionsSummary.key, + event.root.senderId ?: "", + event.senderInfo.disambiguatedDisplayName, + dateFormatter.format(event.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME) + + ) + } + } } .execute { copy(mapReactionKeyToMemberList = it) } } - private fun Observable.toReactionInfoList(): Single> { - return flatMap { summary -> - Observable - .fromIterable(summary.sourceEvents) - .map { - val event = room.getTimeLineEvent(it) - ?: throw RuntimeException("Your eventId is not valid") - ReactionInfo( - event.root.eventId!!, - summary.key, - event.root.senderId ?: "", - event.senderInfo.disambiguatedDisplayName, - dateFormatter.format(event.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME) - - ) - } - }.toList() - } - override fun handle(action: EmptyAction) { // No op } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 1c173e12e8..0e049e22b1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -23,6 +23,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -49,6 +50,8 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.app.features.home.room.list.widget.NotifsFabMenuView import im.vector.app.features.notifications.NotificationDrawerManager +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -118,9 +121,9 @@ class RoomListFragment @Inject constructor( views.createChatFabMenu.listener = this sharedActionViewModel - .observe() - .subscribe { handleQuickActions(it) } - .disposeOnDestroyView() + .stream() + .onEach { handleQuickActions(it) } + .launchIn(viewLifecycleOwner.lifecycleScope) roomListViewModel.onEach(RoomListViewState::roomMembershipChanges) { ms -> // it's for invites local echo diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt index 2b3152f8cf..c98f613c40 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt @@ -20,6 +20,4 @@ import im.vector.app.features.home.RoomListDisplayMode interface RoomListSectionBuilder { fun buildSections(mode: RoomListDisplayMode): List - - fun dispose() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt index f101669af3..58db2a4030 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt @@ -17,6 +17,7 @@ package im.vector.app.features.home.room.list import androidx.annotation.StringRes +import androidx.lifecycle.asFlow import im.vector.app.AppStateHandler import im.vector.app.R import im.vector.app.RoomGroupingMethod @@ -24,17 +25,21 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.showInvites -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.rx.asObservable class RoomListSectionBuilderGroup( + private val coroutineScope: CoroutineScope, private val session: Session, private val stringProvider: StringProvider, private val appStateHandler: AppStateHandler, @@ -42,8 +47,6 @@ class RoomListSectionBuilderGroup( private val onUpdatable: (UpdatableLivePageResult) -> Unit ) : RoomListSectionBuilder { - private val disposables = CompositeDisposable() - override fun buildSections(mode: RoomListDisplayMode): List { val activeGroupAwareQueries = mutableListOf() val sections = mutableListOf() @@ -103,16 +106,14 @@ class RoomListSectionBuilderGroup( appStateHandler.selectedRoomGroupingObservable .distinctUntilChanged() - .subscribe { groupingMethod -> + .onEach { groupingMethod -> val selectedGroupId = (groupingMethod.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId activeGroupAwareQueries.onEach { updater -> updater.updateQuery { query -> query.copy(activeGroupId = selectedGroupId) } } - }.also { - disposables.add(it) - } + }.launchIn(coroutineScope) return sections } @@ -251,15 +252,14 @@ class RoomListSectionBuilderGroup( }.livePagedList .let { livePagedList -> // use it also as a source to update count - livePagedList.asObservable() - .observeOn(Schedulers.computation()) - .subscribe { + livePagedList.asFlow() + .onEach { sections.find { it.sectionName == name } ?.notificationCount ?.postValue(session.getNotificationCountForRooms(roomQueryParams)) - }.also { - disposables.add(it) } + .flowOn(Dispatchers.Default) + .launchIn(coroutineScope) sections.add( RoomsSection( @@ -280,8 +280,4 @@ class RoomListSectionBuilderGroup( .build() .let { block(it) } } - - override fun dispose() { - disposables.dispose() - } } 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 7063281853..0bf7087618 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 @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.list import androidx.annotation.StringRes import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asFlow import androidx.lifecycle.liveData import androidx.paging.PagedList import com.airbnb.mvrx.Async @@ -31,10 +32,17 @@ import im.vector.app.features.invite.showInvites import im.vector.app.space import io.reactivex.Observable import io.reactivex.disposables.CompositeDisposable -import io.reactivex.rxkotlin.Observables import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter @@ -45,6 +53,7 @@ import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.rx.asObservable +import timber.log.Timber class RoomListSectionBuilderSpace( private val session: Session, @@ -57,8 +66,6 @@ class RoomListSectionBuilderSpace( private val onlyOrphansInHome: Boolean = false ) : RoomListSectionBuilder { - private val disposables = CompositeDisposable() - private val pagedListConfig = PagedList.Config.Builder() .setPageSize(10) .setInitialLoadSizeHint(20) @@ -132,14 +139,12 @@ class RoomListSectionBuilderSpace( appStateHandler.selectedRoomGroupingObservable .distinctUntilChanged() - .subscribe { groupingMethod -> + .onEach { groupingMethod -> val selectedSpace = groupingMethod.orNull()?.space() activeSpaceAwareQueries.onEach { updater -> updater.updateForSpaceId(selectedSpace?.roomId) } - }.also { - disposables.add(it) - } + }.launchIn(viewModelScope) return sections } @@ -221,13 +226,13 @@ class RoomListSectionBuilderSpace( } // add suggested rooms - val suggestedRoomsObservable = // MutableLiveData>() + val suggestedRoomsFlow = // MutableLiveData>() appStateHandler.selectedRoomGroupingObservable .distinctUntilChanged() - .switchMap { groupingMethod -> + .flatMapLatest { groupingMethod -> val selectedSpace = groupingMethod.orNull()?.space() if (selectedSpace == null) { - Observable.just(emptyList()) + flowOf(emptyList()) } else { liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) { val spaceSum = tryOrNull { @@ -240,24 +245,23 @@ class RoomListSectionBuilderSpace( session.getRoomSummary(it.childRoomId)?.membership?.isActive() != true } emit(filtered) - }.asObservable() + }.asFlow() } } val liveSuggestedRooms = MutableLiveData() - Observables.combineLatest( - suggestedRoomsObservable, - suggestedRoomJoiningState.asObservable() + combine( + suggestedRoomsFlow, + suggestedRoomJoiningState.asFlow() ) { rooms, joinStates -> SuggestedRoomInfo( rooms, joinStates ) - }.subscribe { + }.onEach { liveSuggestedRooms.postValue(it) - }.also { - disposables.add(it) - } + }.launchIn(viewModelScope) + sections.add( RoomsSection( sectionName = stringProvider.getString(R.string.suggested_header), @@ -373,9 +377,9 @@ class RoomListSectionBuilderSpace( }.livePagedList .let { livePagedList -> // use it also as a source to update count - livePagedList.asObservable() - .observeOn(Schedulers.computation()) - .subscribe { + livePagedList.asFlow() + .onEach { + Timber.v("Thread space list: ${Thread.currentThread()}") sections.find { it.sectionName == name } ?.notificationCount ?.postValue( @@ -387,9 +391,9 @@ class RoomListSectionBuilderSpace( ) } ) - }.also { - disposables.add(it) } + .flowOn(Dispatchers.Default) + .launchIn(viewModelScope) sections.add( RoomsSection( @@ -432,8 +436,4 @@ class RoomListSectionBuilderSpace( RoomListViewModel.SpaceFilterStrategy.NONE -> this } } - - override fun dispose() { - disposables.dispose() - } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 89f5aec8fb..2fd55eb7e5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -135,6 +135,7 @@ class RoomListViewModel @AssistedInject constructor( ) } else { RoomListSectionBuilderGroup( + viewModelScope, session, stringProvider, appStateHandler, @@ -336,8 +337,4 @@ class RoomListViewModel @AssistedInject constructor( } } - override fun onCleared() { - super.onCleared() - roomListSectionBuilder.dispose() - } } diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt index 6f4aff0041..1bf1c12a48 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.os.Bundle import android.os.Parcelable import android.view.View +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -41,6 +42,8 @@ import im.vector.app.features.userdirectory.UserListFragment import im.vector.app.features.userdirectory.UserListFragmentArgs import im.vector.app.features.userdirectory.UserListSharedAction import im.vector.app.features.userdirectory.UserListSharedActionViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.failure.Failure import java.net.HttpURLConnection @@ -63,8 +66,8 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java) sharedActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { UserListSharedAction.Close -> finish() UserListSharedAction.GoBack -> onBackPressed() @@ -75,7 +78,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { } } } - .disposeOnDestroy() + .launchIn(lifecycleScope) if (isFirstCreation()) { addFragment( R.id.container, 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 09eff756d5..a1cc6d6d5e 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 @@ -19,10 +19,14 @@ 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 import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -51,7 +55,8 @@ class InvitesAcceptor @Inject constructor( private val autoAcceptInvites: AutoAcceptInvites ) : Session.Listener { - private lateinit var activeSessionDisposable: Disposable + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + private val shouldRejectRoomIds = mutableSetOf() private val activeSessionIds = mutableSetOf() private val semaphore = Semaphore(1) @@ -61,13 +66,14 @@ class InvitesAcceptor @Inject constructor( } private fun observeActiveSession() { - activeSessionDisposable = sessionDataSource.observe() + sessionDataSource.stream() .distinctUntilChanged() - .subscribe { + .onEach { it.orNull()?.let { session -> onSessionActive(session) } } + .launchIn(coroutineScope) } private fun onSessionActive(session: Session) { diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt index b3606a68ca..bcde8fd37e 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt @@ -85,10 +85,9 @@ open class LoginActivity : VectorBaseActivity(), ToolbarCo addFirstFragment() } - loginViewModel - .subscribe(this) { - updateWithState(it) - } + loginViewModel.onEach { + updateWithState(it) + } loginViewModel.observeViewEvents { handleLoginViewEvents(it) } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt index 40dd1d2872..8f1b20aa7f 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt @@ -92,10 +92,9 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC addFirstFragment() } - loginViewModel - .subscribe(this) { - updateWithState(it) - } + loginViewModel.onEach { + updateWithState(it) + } loginViewModel.observeViewEvents { handleLoginViewEvents(it) } @@ -201,19 +200,19 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC // Go back to the login fragment supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) } - is LoginViewEvents2.OnSendEmailSuccess -> + is LoginViewEvents2.OnSendEmailSuccess -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginWaitForEmailFragment2::class.java, LoginWaitForEmailFragmentArgument(event.email), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) - is LoginViewEvents2.OpenSigninPasswordScreen -> { + is LoginViewEvents2.OpenSigninPasswordScreen -> { addFragmentToBackstack(R.id.loginFragmentContainer, LoginFragmentSigninPassword2::class.java, tag = FRAGMENT_LOGIN_TAG, option = commonOption) } - is LoginViewEvents2.OpenSignupPasswordScreen -> { + is LoginViewEvents2.OpenSignupPasswordScreen -> { addFragmentToBackstack(R.id.loginFragmentContainer, LoginFragmentSignupPassword2::class.java, tag = FRAGMENT_REGISTRATION_STAGE_TAG, diff --git a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt index 767d6f1ba7..d8857b3be3 100644 --- a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt +++ b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt @@ -33,8 +33,8 @@ class PowerLevelsFlowFactory(private val room: Room) { fun createFlow(): Flow { return room.flow() .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) - .flowOn(Dispatchers.Default) .mapOptional { it.content.toModel() } + .flowOn(Dispatchers.Default) .unwrap() } } diff --git a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt index 6ad93abe0c..d2ee3a56ec 100644 --- a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt @@ -77,8 +77,8 @@ class RequireActiveMembershipViewModel @AssistedInject constructor( room.flow() .liveRoomSummary() .unwrap() - .flowOn(Dispatchers.Default) .map { mapToLeftViewEvent(room, it) } + .flowOn(Dispatchers.Default) } .unwrap() .onEach { event -> diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt index dd4011a865..e59cfafa42 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt @@ -19,6 +19,7 @@ package im.vector.app.features.roomdirectory import android.content.Context import android.content.Intent import android.os.Bundle +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint @@ -31,6 +32,8 @@ import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.features.roomdirectory.createroom.CreateRoomArgs import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment import im.vector.app.features.roomdirectory.picker.RoomDirectoryPickerFragment +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import javax.inject.Inject @AndroidEntryPoint @@ -53,8 +56,8 @@ class RoomDirectoryActivity : VectorBaseActivity() { } sharedActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { is RoomDirectorySharedAction.Back -> popBackstack() is RoomDirectorySharedAction.CreateRoom -> { @@ -72,7 +75,7 @@ class RoomDirectoryActivity : VectorBaseActivity() { is RoomDirectorySharedAction.Close -> finish() } } - .disposeOnDestroy() + .launchIn(lifecycleScope) } override fun initUiAndData() { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt index eeb7d217c0..b3a21dadb9 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt @@ -19,6 +19,7 @@ package im.vector.app.features.roomdirectory.createroom import android.content.Context import android.content.Intent import android.os.Bundle +import androidx.lifecycle.lifecycleScope import com.google.android.material.appbar.MaterialToolbar import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R @@ -28,6 +29,8 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.features.roomdirectory.RoomDirectorySharedAction import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach /** * Simple container for [CreateRoomFragment] @@ -62,14 +65,14 @@ class CreateRoomActivity : VectorBaseActivity(), ToolbarC super.onCreate(savedInstanceState) sharedActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java) sharedActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { is RoomDirectorySharedAction.Back, is RoomDirectorySharedAction.Close -> finish() } } - .disposeOnDestroy() + .launchIn(lifecycleScope) } companion object { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt index c61da211a4..1244a0f64e 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -23,6 +23,7 @@ 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.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.args @@ -44,6 +45,8 @@ import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel import im.vector.app.features.roomprofile.settings.joinrule.toOption +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.model.RoomJoinRules @@ -103,11 +106,11 @@ class CreateRoomFragment @Inject constructor( private fun setupRoomJoinRuleSharedActionViewModel() { roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java) roomJoinRuleSharedActionViewModel - .observe() - .subscribe { action -> + .stream() + .onEach { action -> viewModel.handle(CreateRoomAction.SetVisibility(action.roomJoinRule)) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } override fun showFailure(throwable: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index fdb639e7d6..c06a2927c5 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -20,6 +20,7 @@ package im.vector.app.features.roomprofile import android.content.Context import android.content.Intent import android.widget.Toast +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.google.android.material.appbar.MaterialToolbar @@ -41,6 +42,8 @@ import im.vector.app.features.roomprofile.notifications.RoomNotificationSettings import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import javax.inject.Inject @AndroidEntryPoint @@ -93,8 +96,8 @@ class RoomProfileActivity : } } sharedActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() @@ -105,7 +108,7 @@ class RoomProfileActivity : RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() }.exhaustive } - .disposeOnDestroy() + .launchIn(lifecycleScope) requireActiveMembershipViewModel.observeViewEvents { when (it) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 23234f8bbd..e1a5cae907 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -26,6 +26,7 @@ import android.view.ViewGroup import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.view.isVisible import androidx.fragment.app.setFragmentResultListener +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -52,6 +53,8 @@ import im.vector.app.features.home.room.list.actions.RoomListActionsArgs import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.api.util.toMatrixItem @@ -124,9 +127,9 @@ class RoomProfileFragment @Inject constructor( }.exhaustive } roomListQuickActionsSharedActionViewModel - .observe() - .subscribe { handleQuickActions(it) } - .disposeOnDestroyView() + .stream() + .onEach { handleQuickActions(it) } + .launchIn(viewLifecycleOwner.lifecycleScope) setupClicks() setupLongClicks() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index e281c0f84d..15686a6848 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -21,6 +21,7 @@ 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.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -38,6 +39,8 @@ import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedAction import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.util.toMatrixItem @@ -77,9 +80,9 @@ class RoomAliasFragment @Inject constructor( } sharedActionViewModel - .observe() - .subscribe { handleAliasAction(it) } - .disposeOnDestroyView() + .stream() + .onEach { handleAliasAction(it) } + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun handleAliasAction(action: RoomAliasBottomSheetSharedAction?) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index adf5a31f2a..1ea9d59229 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -95,7 +95,6 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState if (room.isEncrypted()) { room.flow().liveRoomMembers(roomMemberQueryParams) - .flowOn(Dispatchers.Main) .flatMapLatest { membersSummary -> session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId }) .asFlow() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index ce059881b8..0a5f8f4d9a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -24,6 +24,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -46,6 +47,8 @@ import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistory import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleActivity import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.util.toMatrixItem import java.util.UUID @@ -101,21 +104,21 @@ class RoomSettingsFragment @Inject constructor( private fun setupRoomJoinRuleSharedActionViewModel() { roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java) roomJoinRuleSharedActionViewModel - .observe() - .subscribe { action -> + .stream() + .onEach { action -> viewModel.handle(RoomSettingsAction.SetRoomJoinRule(action.roomJoinRule)) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun setupRoomHistoryVisibilitySharedActionViewModel() { roomHistoryVisibilitySharedActionViewModel = activityViewModelProvider.get(RoomHistoryVisibilitySharedActionViewModel::class.java) roomHistoryVisibilitySharedActionViewModel - .observe() - .subscribe { action -> + .stream() + .onEach { action -> viewModel.handle(RoomSettingsAction.SetRoomHistoryVisibility(action.roomHistoryVisibility)) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun showSuccess() { 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 b622d8aab4..2c32070c76 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 @@ -149,11 +149,11 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( refreshMyDevice() refreshXSigningStatus() session.liveSecretSynchronisationInfo() - .flowOn(Dispatchers.Main) .onEach { refresh4SSection(it) refreshXSigningStatus() - }.launchIn(viewLifecycleOwner.lifecycleScope) + } + .launchIn(viewLifecycleOwner.lifecycleScope) lifecycleScope.launchWhenResumed { findPreference(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible = 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 e8300a1097..154518778d 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 @@ -31,6 +31,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel 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 @@ -66,6 +67,7 @@ import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber import java.util.concurrent.TimeUnit import javax.net.ssl.HttpsURLConnection +import javax.sql.DataSource import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -103,7 +105,7 @@ class DevicesViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - private val refreshPublisher: PublishSubject = PublishSubject.create() + private val refreshSource= PublishDataSource() init { @@ -166,12 +168,12 @@ class DevicesViewModel @AssistedInject constructor( // ) // } - refreshPublisher.throttleFirst(4_000, TimeUnit.MILLISECONDS) - .subscribe { + refreshSource.stream().sample(4_000) + .onEach { session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) session.cryptoService().downloadKeys(listOf(session.myUserId), true, NoOpMatrixCallback()) } - .disposeOnClear() + .launchIn(viewModelScope) // then force download queryRefreshDevicesList() } @@ -193,7 +195,7 @@ class DevicesViewModel @AssistedInject constructor( * It can be any mobile devices, and any browsers. */ private fun queryRefreshDevicesList() { - refreshPublisher.onNext(Unit) + refreshSource.post(Unit) } override fun handle(action: DevicesAction) { diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt index 6e70b34002..4fba8fc3de 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt @@ -50,7 +50,7 @@ class SoftLogoutActivity : LoginActivity() { override fun initUiAndData() { super.initUiAndData() - softLogoutViewModel.subscribe(this) { + softLogoutViewModel.onEach { updateWithState(it) } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt index ed45069e92..26500b60f0 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt @@ -52,7 +52,7 @@ class SoftLogoutActivity2 : LoginActivity2() { override fun initUiAndData() { super.initUiAndData() - softLogoutViewModel.subscribe(this) { + softLogoutViewModel.onEach { updateWithState(it) } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt index 2aa7f15172..016d340f80 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt @@ -55,7 +55,7 @@ class SoftLogoutFragment @Inject constructor( setupRecyclerView() - softLogoutViewModel.subscribe(this) { softLogoutViewState -> + softLogoutViewModel.onEach { softLogoutViewState -> softLogoutController.update(softLogoutViewState) when (val mode = softLogoutViewState.asyncHomeServerLoginFlowRequest.invoke()) { is LoginMode.SsoAndPassword -> { diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt index 75373775f9..44acfa8ee3 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt @@ -71,7 +71,7 @@ class SpaceCreationActivity : SimpleFragmentActivity() { override fun initUiAndData() { super.initUiAndData() - viewModel.subscribe(this) { + viewModel.onEach { renderState(it) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt index 4487833773..a762e13cba 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt @@ -35,6 +35,7 @@ import im.vector.app.group import im.vector.app.space import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -89,14 +90,11 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa // observeSelectionState() appStateHandler.selectedRoomGroupingObservable .distinctUntilChanged() - .subscribe { - setState { - copy( - selectedGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null) - ) - } + .setOnEach { + copy( + selectedGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null) + ) } - .disposeOnClear() session.getGroupSummariesLive(groupSummaryQueryParams {}) .asFlow() @@ -114,7 +112,6 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa }, sortOrder = RoomSortOrder.NONE ).asFlow() .sample(300) - .flowOn(Dispatchers.Default) .onEach { val inviteCount = if (autoAcceptInvites.hideInvites) { 0 @@ -140,7 +137,9 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa homeAggregateCount = counts ) } - }.launchIn(viewModelScope) + } + .flowOn(Dispatchers.Default) + .launchIn(viewModelScope) } override fun handle(action: SpaceListAction) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt index 59166529b9..fcbbf6a752 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt @@ -19,6 +19,7 @@ package im.vector.app.features.spaces import android.content.Context import android.content.Intent import android.os.Bundle +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import im.vector.app.R import im.vector.app.core.extensions.commitTransaction @@ -26,6 +27,8 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.features.spaces.preview.SpacePreviewArgs import im.vector.app.features.spaces.preview.SpacePreviewFragment +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach class SpacePreviewActivity : VectorBaseActivity() { @@ -37,8 +40,8 @@ class SpacePreviewActivity : VectorBaseActivity() { super.onCreate(savedInstanceState) sharedActionViewModel = viewModelProvider.get(SpacePreviewSharedActionViewModel::class.java) sharedActionViewModel - .observe() - .subscribe { action -> + .stream() + .onEach { action -> when (action) { SpacePreviewSharedAction.DismissAction -> finish() SpacePreviewSharedAction.ShowModalLoading -> showWaitingView() @@ -46,7 +49,7 @@ class SpacePreviewActivity : VectorBaseActivity() { is SpacePreviewSharedAction.ShowErrorMessage -> action.error?.let { showSnackbar(it) } } } - .disposeOnDestroy() + .launchIn(lifecycleScope) if (isFirstCreation()) { val simpleName = SpacePreviewFragment::class.java.simpleName diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt index 6dc3ad8c21..4328c46188 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt @@ -50,7 +50,7 @@ class CreateSpaceAdd3pidInvitesFragment @Inject constructor( views.recyclerView.configureWith(epoxyController) epoxyController.listener = this - sharedViewModel.subscribe(this) { + sharedViewModel.onEach { invalidateState(it) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt index 53a4ee689b..4ed7e91417 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt @@ -46,7 +46,7 @@ class CreateSpaceDefaultRoomsFragment @Inject constructor( views.recyclerView.configureWith(epoxyController) epoxyController.listener = this - sharedViewModel.subscribe(this) { + sharedViewModel.onEach { epoxyController.setData(it) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt index 544c33948b..920ceed33c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt @@ -50,7 +50,7 @@ class CreateSpaceDetailsFragment @Inject constructor( views.recyclerView.configureWith(epoxyController) epoxyController.listener = this - sharedViewModel.subscribe(this) { + sharedViewModel.onEach { epoxyController.setData(it) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt index 541d883405..69de39e436 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt @@ -86,7 +86,7 @@ class SpaceLeaveAdvancedActivity : VectorBaseActivity + leaveViewModel.onEach { state -> when (state.leaveState) { is Loading -> { showWaitingView() diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt index 2dae088c2e..932110d0e3 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt @@ -22,6 +22,7 @@ import android.os.Bundle import android.os.Parcelable import androidx.core.view.isGone import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.withState @@ -41,6 +42,8 @@ import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.alias.RoomAliasFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize @Parcelize @@ -80,14 +83,14 @@ class SpaceManageActivity : VectorBaseActivity(), sharedDirectoryActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java) sharedDirectoryActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { is RoomDirectorySharedAction.Back, is RoomDirectorySharedAction.Close -> finish() } } - .disposeOnDestroy() + .launchIn(lifecycleScope) val args = intent?.getParcelableExtra(Mavericks.KEY_ARG) if (isFirstCreation()) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt index c2ab015858..a0ab055311 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt @@ -24,6 +24,7 @@ import android.view.MenuItem 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.args import com.airbnb.mvrx.fragmentViewModel @@ -49,6 +50,8 @@ import im.vector.app.features.roomprofile.settings.RoomSettingsViewModel import im.vector.app.features.roomprofile.settings.RoomSettingsViewState import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleActivity import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules @@ -142,11 +145,11 @@ class SpaceSettingsFragment @Inject constructor( private fun setupRoomJoinRuleSharedActionViewModel() { roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java) roomJoinRuleSharedActionViewModel - .observe() - .subscribe { action -> + .stream() + .onEach { action -> viewModel.handle(RoomSettingsAction.SetRoomJoinRule(action.roomJoinRule)) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private var ignoreChanges = false diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt index 3b84a12bc1..bc778a1b34 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.os.Bundle import androidx.core.view.isGone import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import im.vector.app.R import im.vector.app.core.extensions.commitTransaction @@ -29,6 +30,8 @@ import im.vector.app.core.platform.GenericIdArgs import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleLoadingBinding import im.vector.app.features.spaces.share.ShareSpaceBottomSheet +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach class SpacePeopleActivity : VectorBaseActivity() { @@ -73,8 +76,8 @@ class SpacePeopleActivity : VectorBaseActivity() { sharedActionViewModel = viewModelProvider.get(SpacePeopleSharedActionViewModel::class.java) sharedActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { SpacePeopleSharedAction.Dismiss -> finish() is SpacePeopleSharedAction.NavigateToRoom -> navigateToRooms(sharedAction) @@ -86,7 +89,7 @@ class SpacePeopleActivity : VectorBaseActivity() { ShareSpaceBottomSheet.show(supportFragmentManager, sharedAction.spaceId) } } - }.disposeOnDestroy() + }.launchIn(lifecycleScope) } private fun navigateToRooms(action: SpacePeopleSharedAction.NavigateToRoom) { 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 6e14893f77..dad8ecfcca 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 @@ -91,7 +91,7 @@ class SpacePeopleFragment @Inject constructor( handleViewEvents(it) } - viewModel.subscribe(this) { + viewModel.onEach { when (it.createAndInviteState) { is Loading -> sharedActionViewModel.post(SpacePeopleSharedAction.ShowModalLoading) Uninitialized, diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index fde69ce9ba..c7ffc43727 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -160,10 +160,10 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User knownUsersSearch .sample(300) - .flowOn(Dispatchers.Main) .flatMapLatest { search -> session.getPagedUsersLive(search, state.excludedUserIds).asFlow() - }.execute { + } + .execute { copy(knownUsers = it) } diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt index f2a087fd52..5c0cfd265b 100644 --- a/vector/src/test/java/im/vector/app/test/Extensions.kt +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt @@ -27,17 +27,17 @@ fun String.trimIndentOneLine() = trimIndent().replace("\n", "") fun VectorViewModel.test(): ViewModelTest { val state = { com.airbnb.mvrx.withState(this) { it } } - val viewEvents = viewEvents.observe().test() - return ViewModelTest(state, viewEvents) + //val viewEvents = viewEvents.stream().test() + return ViewModelTest(state) } class ViewModelTest( val state: () -> S, - val viewEvents: TestObserver + //val viewEvents: TestObserver ) { fun assertEvents(vararg expected: VE) { - viewEvents.assertValues(*expected) + //viewEvents.assertValues(*expected) } fun assertState(expected: S) { From 9479342a643aee1803ffeadb4bed27790c4ca397 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Oct 2021 15:16:10 +0200 Subject: [PATCH 02/12] Flow: remove more rx --- dependencies.gradle | 1 - vector/build.gradle | 1 - .../vector/app/features/home/HomeActivity.kt | 3 +-- .../app/features/home/HomeDetailViewModel.kt | 2 -- .../app/features/home/ShortcutsHandler.kt | 20 ++++++++++--------- .../home/UnreadMessagesSharedViewModel.kt | 2 -- .../room/list/RoomListSectionBuilderSpace.kt | 1 - .../settings/SecretsSynchronisationInfo.kt | 1 - .../VectorSettingsSecurityPrivacyFragment.kt | 1 - 9 files changed, 12 insertions(+), 20 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index 1e77b6354b..776e89f9b3 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -102,7 +102,6 @@ ext.libs = [ 'epoxyProcessor' : "com.airbnb.android:epoxy-processor:$epoxy", 'epoxyPaging' : "com.airbnb.android:epoxy-paging:$epoxy", 'mavericks' : "com.airbnb.android:mavericks:$mavericks", - 'mavericksRx' : "com.airbnb.android:mavericks-rxjava2:$mavericks", 'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks" ], mockk : [ diff --git a/vector/build.gradle b/vector/build.gradle index 35d1acfe8b..a5d10e222b 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -333,7 +333,6 @@ configurations { dependencies { implementation project(":matrix-sdk-android") - implementation project(":matrix-sdk-android-rx") implementation project(":matrix-sdk-android-flow") implementation project(":diff-match-patch") implementation project(":multipicker") diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 04ca25332f..039b7c3e7b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -247,8 +247,7 @@ class HomeActivity : } homeActivityViewModel.onEach { renderState(it) } - shortcutsHandler.observeRoomsAndBuildShortcuts() - .disposeOnDestroy() + shortcutsHandler.observeRoomsAndBuildShortcuts(lifecycleScope) if (!vectorPreferences.didPromoteNewRestrictedFeature()) { promoteRestrictedViewModel.onEach { 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 8bfc1a8db4..a7d361c757 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 @@ -56,9 +56,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.rx.asObservable import timber.log.Timber -import java.util.concurrent.TimeUnit /** * View model used to update the home bottom bar notification counts, observe the sync state and diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index 7514d455aa..ff553577a0 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -21,13 +21,15 @@ import android.content.pm.ShortcutManager import android.os.Build import androidx.core.content.getSystemService import androidx.core.content.pm.ShortcutManagerCompat +import androidx.lifecycle.asFlow import im.vector.app.core.di.ActiveSessionHolder -import io.reactivex.disposables.Disposable -import io.reactivex.disposables.Disposables +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.rx.asObservable import javax.inject.Inject class ShortcutsHandler @Inject constructor( @@ -36,12 +38,11 @@ class ShortcutsHandler @Inject constructor( private val activeSessionHolder: ActiveSessionHolder ) { - fun observeRoomsAndBuildShortcuts(): Disposable { + fun observeRoomsAndBuildShortcuts(coroutineScope: CoroutineScope): Job { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { // No op - return Disposables.empty() + return Job() } - return activeSessionHolder.getSafeActiveSession() ?.getPagedRoomSummariesLive( roomSummaryQueryParams { @@ -49,8 +50,8 @@ class ShortcutsHandler @Inject constructor( }, sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY ) - ?.asObservable() - ?.subscribe { rooms -> + ?.asFlow() + ?.onEach { rooms -> // Remove dead shortcuts (i.e. deleted rooms) val roomIds = rooms.map { it.roomId } val deadShortcutIds = ShortcutManagerCompat.getShortcuts(context, ShortcutManagerCompat.FLAG_MATCH_DYNAMIC) @@ -66,7 +67,8 @@ class ShortcutsHandler @Inject constructor( ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) } } - ?: Disposables.empty() + ?.launchIn(coroutineScope) + ?: Job() } fun clearShortcuts() { diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index 3434f9dfb0..4cfe0f7546 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -44,8 +44,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount -import org.matrix.android.sdk.rx.asObservable -import java.util.concurrent.TimeUnit data class UnreadMessagesState( val homeSpaceUnread: RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0), 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 0bf7087618..1e806bb2d8 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 @@ -52,7 +52,6 @@ import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount -import org.matrix.android.sdk.rx.asObservable import timber.log.Timber class RoomListSectionBuilderSpace( diff --git a/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt b/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt index 5afcb77587..e21366db02 100644 --- a/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt +++ b/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt @@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NA import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.rx.SecretsSynchronisationInfo data class SecretsSynchronisationInfo( val isBackupSetup: Boolean, 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 2c32070c76..ca96d6a6bc 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 @@ -71,7 +71,6 @@ import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse -import org.matrix.android.sdk.rx.SecretsSynchronisationInfo import javax.inject.Inject class VectorSettingsSecurityPrivacyFragment @Inject constructor( From 8cf5b727e1eba92386bcf813f18a7e42f14779e4 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Oct 2021 15:57:18 +0200 Subject: [PATCH 03/12] Flow: restore read receipts --- .../im/vector/app/core/flow/ChunkOperator.kt | 79 +++++++++++++++++++ .../home/room/detail/RoomDetailViewModel.kt | 20 +++-- 2 files changed, 91 insertions(+), 8 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/flow/ChunkOperator.kt diff --git a/vector/src/main/java/im/vector/app/core/flow/ChunkOperator.kt b/vector/src/main/java/im/vector/app/core/flow/ChunkOperator.kt new file mode 100644 index 0000000000..533a060883 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/flow/ChunkOperator.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.flow + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ClosedReceiveChannelException +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.produce +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.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" } + return flow { + coroutineScope { + val events = ArrayList() + val ticker = fixedPeriodTicker(durationInMillis) + try { + val upstreamValues = produce(capacity = Channel.CONFLATED) { + collect { value -> send(value) } + } + while (isActive) { + var hasTimedOut = false + select { + upstreamValues.onReceive { + events.add(it) + } + ticker.onReceive { + hasTimedOut = true + } + } + if (hasTimedOut && events.isNotEmpty()) { + emit(events.toList()) + events.clear() + } + } + } catch (e: ClosedReceiveChannelException) { + // drain remaining events + if (events.isNotEmpty()) emit(events.toList()) + } finally { + ticker.cancel() + } + } + } +} + +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" } + return produce(capacity = 0) { + delay(initialDelayMillis) + while (true) { + channel.send(Unit) + delay(delayMillis) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index ee929243b5..1b765f81b9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -33,6 +33,7 @@ import dagger.assisted.AssistedInject import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.flow.chunk import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider @@ -58,13 +59,17 @@ import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.voice.VoicePlayerHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance +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 kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixPatterns @@ -863,13 +868,13 @@ class RoomDetailViewModel @AssistedInject constructor( private fun observeEventDisplayedActions() { // We are buffering scroll events for one second // and keep the most recent one to set the read receipt on. - /* + visibleEventsSource .stream() - .buffer(1, TimeUnit.SECONDS) + .chunk(1000) .filter { it.isNotEmpty() } - .subscribeBy(onNext = { actions -> - val bufferedMostRecentDisplayedEvent = actions.maxByOrNull { it.event.displayIndex }?.event ?: return@subscribeBy + .onEach { actions -> + val bufferedMostRecentDisplayedEvent = actions.maxByOrNull { it.event.displayIndex }?.event ?: return@onEach val globalMostRecentDisplayedEvent = mostRecentDisplayedEvent if (trackUnreadMessages.get()) { if (globalMostRecentDisplayedEvent == null) { @@ -883,10 +888,9 @@ class RoomDetailViewModel @AssistedInject constructor( tryOrNull { room.setReadReceipt(eventId) } } } - }) - .disposeOnClear() - - */ + } + .flowOn(Dispatchers.Default) + .launchIn(viewModelScope) } private fun handleMarkAllAsRead() { From a9d192fa399c6bddcc6d3bdede485f64f7765a7e Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Oct 2021 18:09:07 +0200 Subject: [PATCH 04/12] Flow migration: add back some test --- dependencies.gradle | 3 +- vector/build.gradle | 2 + .../quads/SharedSecureStorageViewModelTest.kt | 110 +++++++++++------- .../java/im/vector/app/test/Extensions.kt | 22 ++-- .../im/vector/app/test/FlowTestObserver.kt | 53 +++++++++ 5 files changed, 138 insertions(+), 52 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/test/FlowTestObserver.kt diff --git a/dependencies.gradle b/dependencies.gradle index 776e89f9b3..15660f17f2 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -41,7 +41,8 @@ ext.libs = [ jetbrains : [ 'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines", 'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines", - 'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines" + 'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines", + 'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines" ], androidx : [ 'appCompat' : "androidx.appcompat:appcompat:1.3.1", diff --git a/vector/build.gradle b/vector/build.gradle index a5d10e222b..493a26dcc6 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -508,6 +508,7 @@ dependencies { // Plant Timber tree for test testImplementation libs.tests.timberJunitRule testImplementation libs.airbnb.mavericksTesting + testImplementation libs.jetbrains.coroutinesTest // Activate when you want to check for leaks, from time to time. //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3' @@ -521,6 +522,7 @@ dependencies { androidTestImplementation libs.androidx.espressoIntents androidTestImplementation libs.tests.kluent androidTestImplementation libs.androidx.coreTesting + androidTestImplementation libs.jetbrains.coroutinesTest // Plant Timber tree for test androidTestImplementation libs.tests.timberJunitRule // "The one who serves a great Espresso" diff --git a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt index 506ac9c7d0..00828acbb8 100644 --- a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt @@ -22,6 +22,7 @@ import im.vector.app.test.InstantRxRule import im.vector.app.test.fakes.FakeSession import im.vector.app.test.fakes.FakeStringProvider import im.vector.app.test.test +import kotlinx.coroutines.test.runBlockingTest import org.junit.Rule import org.junit.Test import org.matrix.android.sdk.api.session.securestorage.IntegrityResult @@ -41,6 +42,7 @@ class SharedSecureStorageViewModelTest { @get:Rule val instantRx = InstantRxRule() + @get:Rule val mvrxTestRule = MvRxTestRule() @@ -50,78 +52,100 @@ class SharedSecureStorageViewModelTest { @Test fun `given a key info with passphrase when initialising then step is EnterPassphrase`() { - givenKey(KEY_INFO_WITH_PASSPHRASE) - - val viewModel = createViewModel() - - viewModel.test().assertState(aViewState( - hasPassphrase = true, - step = SharedSecureStorageViewState.Step.EnterPassphrase - )) + runBlockingTest { + givenKey(KEY_INFO_WITH_PASSPHRASE) + val viewModel = createViewModel() + viewModel + .test(this) + .assertState(aViewState( + hasPassphrase = true, + step = SharedSecureStorageViewState.Step.EnterPassphrase + )) + .finish() + } } @Test fun `given a key info without passphrase when initialising then step is EnterKey`() { - givenKey(KEY_INFO_WITHOUT_PASSPHRASE) + runBlockingTest { + givenKey(KEY_INFO_WITHOUT_PASSPHRASE) - val viewModel = createViewModel() + val viewModel = createViewModel() - viewModel.test().assertState(aViewState( - hasPassphrase = false, - step = SharedSecureStorageViewState.Step.EnterKey - )) + viewModel + .test(this) + .assertState(aViewState( + hasPassphrase = false, + step = SharedSecureStorageViewState.Step.EnterKey + )) + .finish() + } } @Test fun `given on EnterKey step when going back then dismisses`() { - givenKey(KEY_INFO_WITHOUT_PASSPHRASE) + runBlockingTest { + givenKey(KEY_INFO_WITHOUT_PASSPHRASE) - val viewModel = createViewModel() - val test = viewModel.test() - - viewModel.handle(SharedSecureStorageAction.Back) - - test.assertEvents(SharedSecureStorageViewEvent.Dismiss) + val viewModel = createViewModel() + val test = viewModel.test(this) + viewModel.handle(SharedSecureStorageAction.Back) + test + .assertEvents(SharedSecureStorageViewEvent.Dismiss) + .finish() + } } @Test fun `given on passphrase step when using key then step is EnterKey`() { - givenKey(KEY_INFO_WITH_PASSPHRASE) - val viewModel = createViewModel() - val test = viewModel.test() + runBlockingTest { + givenKey(KEY_INFO_WITH_PASSPHRASE) + val viewModel = createViewModel() + val test = viewModel.test(this) - viewModel.handle(SharedSecureStorageAction.UseKey) + viewModel.handle(SharedSecureStorageAction.UseKey) - test.assertState(aViewState( - hasPassphrase = true, - step = SharedSecureStorageViewState.Step.EnterKey - )) + test + .assertState(aViewState( + hasPassphrase = true, + step = SharedSecureStorageViewState.Step.EnterKey + )) + .finish() + } } @Test fun `given a key info with passphrase and on EnterKey step when going back then step is EnterPassphrase`() { - givenKey(KEY_INFO_WITH_PASSPHRASE) - val viewModel = createViewModel() - val test = viewModel.test() + runBlockingTest { + givenKey(KEY_INFO_WITH_PASSPHRASE) + val viewModel = createViewModel() + val test = viewModel.test(this) - viewModel.handle(SharedSecureStorageAction.UseKey) - viewModel.handle(SharedSecureStorageAction.Back) + viewModel.handle(SharedSecureStorageAction.UseKey) + viewModel.handle(SharedSecureStorageAction.Back) - test.assertState(aViewState( - hasPassphrase = true, - step = SharedSecureStorageViewState.Step.EnterPassphrase - )) + test + .assertState(aViewState( + hasPassphrase = true, + step = SharedSecureStorageViewState.Step.EnterPassphrase + )) + .finish() + } } @Test fun `given on passphrase step when going back then dismisses`() { - givenKey(KEY_INFO_WITH_PASSPHRASE) - val viewModel = createViewModel() - val test = viewModel.test() + runBlockingTest { + givenKey(KEY_INFO_WITH_PASSPHRASE) + val viewModel = createViewModel() + val test = viewModel.test(this) - viewModel.handle(SharedSecureStorageAction.Back) + viewModel.handle(SharedSecureStorageAction.Back) - test.assertEvents(SharedSecureStorageViewEvent.Dismiss) + test + .assertEvents(SharedSecureStorageViewEvent.Dismiss) + .finish() + } } private fun createViewModel(): SharedSecureStorageViewModel { diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt index 5c0cfd265b..67b5090785 100644 --- a/vector/src/test/java/im/vector/app/test/Extensions.kt +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt @@ -20,27 +20,33 @@ import com.airbnb.mvrx.MavericksState import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction -import io.reactivex.observers.TestObserver +import kotlinx.coroutines.CoroutineScope import org.amshove.kluent.shouldBeEqualTo fun String.trimIndentOneLine() = trimIndent().replace("\n", "") -fun VectorViewModel.test(): ViewModelTest { +fun VectorViewModel.test(coroutineScope: CoroutineScope): ViewModelTest { val state = { com.airbnb.mvrx.withState(this) { it } } - //val viewEvents = viewEvents.stream().test() - return ViewModelTest(state) + val viewEvents = viewEvents.stream().test(coroutineScope) + return ViewModelTest(state, viewEvents) } class ViewModelTest( val state: () -> S, - //val viewEvents: TestObserver + val viewEvents: FlowTestObserver ) { - fun assertEvents(vararg expected: VE) { - //viewEvents.assertValues(*expected) + fun assertEvents(vararg expected: VE): ViewModelTest { + viewEvents.assertValues(*expected) + return this } - fun assertState(expected: S) { + fun assertState(expected: S): ViewModelTest { state() shouldBeEqualTo expected + return this + } + + fun finish(){ + viewEvents.finish() } } diff --git a/vector/src/test/java/im/vector/app/test/FlowTestObserver.kt b/vector/src/test/java/im/vector/app/test/FlowTestObserver.kt new file mode 100644 index 0000000000..955922d0c4 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/FlowTestObserver.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import org.junit.Assert.assertEquals + +fun Flow.test(scope: CoroutineScope): FlowTestObserver { + return FlowTestObserver(scope, this) +} + +class FlowTestObserver( + scope: CoroutineScope, + flow: Flow +) { + private val values = mutableListOf() + private val job: Job = flow + .onEach { + values.add(it) + }.launchIn(scope) + + fun assertNoValues(): FlowTestObserver { + assertEquals(emptyList(), this.values) + return this + } + + fun assertValues(vararg values: T): FlowTestObserver { + assertEquals(values.toList(), this.values) + return this + } + + fun finish() { + job.cancel() + } +} From 34cb99e8aec12526bc242f8238787443fb5f6564 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 27 Oct 2021 12:13:49 +0200 Subject: [PATCH 05/12] 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() From b93e67ed11b113e7f717a4e339ae13a41d5dd485 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 27 Oct 2021 16:12:01 +0200 Subject: [PATCH 06/12] Flow migration: clean up --- .../src/main/java/im/vector/app/core/di/FragmentModule.kt | 2 +- .../java/im/vector/app/core/platform/VectorBaseFragment.kt | 2 -- vector/src/main/java/im/vector/app/core/rx/RxConfig.kt | 1 - .../java/im/vector/app/features/call/VectorCallActivity.kt | 3 --- .../app/features/contactsbook/ContactsBookFragment.kt | 1 - .../crypto/quads/SharedSecuredStorageKeyFragment.kt | 2 -- .../crypto/quads/SharedSecuredStoragePassphraseFragment.kt | 2 -- .../crypto/recover/BootstrapConfirmPassphraseFragment.kt | 2 -- .../crypto/recover/BootstrapEnterPassphraseFragment.kt | 2 -- .../crypto/recover/BootstrapMigrateBackupFragment.kt | 2 -- .../java/im/vector/app/features/home/HomeDetailViewModel.kt | 1 - .../app/features/home/UnreadMessagesSharedViewModel.kt | 1 - .../app/features/home/room/detail/RoomDetailFragment.kt | 1 - .../app/features/home/room/detail/RoomDetailViewModel.kt | 3 --- .../features/home/room/list/RoomListDisplayModeFilter.kt | 2 +- .../vector/app/features/home/room/list/RoomListViewModel.kt | 1 - .../app/features/reactions/EmojiReactionPickerActivity.kt | 3 --- .../app/features/roomdirectory/PublicRoomsFragment.kt | 1 - .../features/roomprofile/members/RoomMemberListViewModel.kt | 2 -- .../advanced/RoomJoinRuleChooseRestrictedFragment.kt | 1 - .../settings/VectorSettingsSecurityPrivacyFragment.kt | 2 -- .../app/features/settings/devices/DevicesViewModel.kt | 4 +--- .../im/vector/app/features/spaces/LeaveSpaceBottomSheet.kt | 1 - .../app/features/spaces/leave/SelectChildrenController.kt | 2 +- .../app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt | 1 - .../app/features/spaces/manage/SpaceAddRoomFragment.kt | 1 - .../app/features/spaces/manage/SpaceManageRoomsFragment.kt | 1 - .../app/features/spaces/people/SpacePeopleFragment.kt | 1 - .../vector/app/features/userdirectory/UserListFragment.kt | 3 +-- .../vector/app/features/userdirectory/UserListViewModel.kt | 2 -- vector/src/test/java/im/vector/app/test/Extensions.kt | 6 +++--- 31 files changed, 8 insertions(+), 51 deletions(-) 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 0291765b59..4a77a78c3d 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,8 +111,8 @@ import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment -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.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment 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 0c3494bc40..6f0a944b32 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 @@ -44,13 +44,11 @@ import im.vector.app.core.extensions.singletonEntryPoint 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 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 abstract class VectorBaseFragment : Fragment(), MavericksView { 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 41ceff7e64..066839319e 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 @@ -24,6 +24,5 @@ class RxConfig @Inject constructor() { * Make sure unhandled Rx error does not crash the app in production */ fun setupRxPlugin() { - } } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt index f1b94132e0..2406cbc625 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt @@ -62,9 +62,6 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailArgs import io.github.hyuwah.draggableviewlib.DraggableView import io.github.hyuwah.draggableviewlib.setupDraggable -import io.reactivex.android.schedulers.AndroidSchedulers -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize 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 a02e4d6b89..95c6105912 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 @@ -43,7 +43,6 @@ 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 class ContactsBookFragment @Inject constructor( 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 fbfb538fe3..edeb2f875d 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 @@ -29,14 +29,12 @@ 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 class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment() { 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 4441268d30..ee7a1ec305 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 @@ -28,13 +28,11 @@ 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 class SharedSecuredStoragePassphraseFragment @Inject constructor( 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 48191b08d6..a3580db996 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 @@ -29,13 +29,11 @@ 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 class BootstrapConfirmPassphraseFragment @Inject constructor() : 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 14ab9dc76f..6fbd003017 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 @@ -28,13 +28,11 @@ 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 class BootstrapEnterPassphraseFragment @Inject constructor() : 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 1224f4330e..5cc4cfd0c2 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 @@ -38,7 +38,6 @@ import im.vector.app.core.resources.ColorProvider 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 @@ -46,7 +45,6 @@ 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 class BootstrapMigrateBackupFragment @Inject constructor( 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 778b16e813..b110005c31 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 @@ -44,7 +44,6 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample -import kotlinx.coroutines.flow.switchMap import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index 4cfe0f7546..7091dc3f00 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -115,7 +115,6 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia }, sortOrder = RoomSortOrder.NONE ).asFlow() .sample(300) - } ) { groupingMethod, _ -> when (groupingMethod.orNull()) { 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 b78db9dfea..1cd1221ac0 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 @@ -222,7 +222,6 @@ import reactivecircus.flowbinding.android.widget.textChanges import timber.log.Timber import java.net.URL import java.util.UUID -import java.util.concurrent.TimeUnit import javax.inject.Inject @Parcelize diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 1b765f81b9..d846a1d1f8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -59,7 +59,6 @@ import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.voice.VoicePlayerHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -69,7 +68,6 @@ 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 kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixPatterns @@ -102,7 +100,6 @@ import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import timber.log.Timber -import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean class RoomDetailViewModel @AssistedInject constructor( 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 f0bd070491..94a79f5fbd 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 @@ -16,8 +16,8 @@ package im.vector.app.features.home.room.list -import im.vector.app.features.home.RoomListDisplayMode import androidx.core.util.Predicate +import im.vector.app.features.home.RoomListDisplayMode 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/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 2fd55eb7e5..848ce328e2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -336,5 +336,4 @@ class RoomListViewModel @AssistedInject constructor( _viewEvents.post(value) } } - } 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 2f81ee02e3..3aeab7c876 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 @@ -35,14 +35,11 @@ import im.vector.app.core.extensions.observeEvent 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 /** 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 80223268b5..be1523f4ab 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 @@ -44,7 +44,6 @@ 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 /** diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index 1ea9d59229..0bbdd87f3e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -27,11 +27,9 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.powerlevel.PowerLevelsFlowFactory -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach 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 bb038fe887..b65e90aeed 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 @@ -34,7 +34,6 @@ 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 class RoomJoinRuleChooseRestrictedFragment @Inject constructor( 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 4e6859f5de..438382ab3c 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,8 +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 kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch 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 cf0bb8146e..362237e60d 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 @@ -64,9 +64,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.net.ssl.HttpsURLConnection -import javax.sql.DataSource import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -104,7 +102,7 @@ class DevicesViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - private val refreshSource= PublishDataSource() + private val refreshSource = PublishDataSource() init { 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 d8ee03a8f5..a292b64ddd 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 @@ -43,7 +43,6 @@ import im.vector.app.core.utils.styleMatchingText 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 diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SelectChildrenController.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SelectChildrenController.kt index 239ff5518f..6260c136d9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SelectChildrenController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SelectChildrenController.kt @@ -16,6 +16,7 @@ package im.vector.app.features.spaces.leave +import androidx.core.util.Predicate import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -27,7 +28,6 @@ import im.vector.app.core.epoxy.noResultItem import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.spaces.manage.roomSelectionItem -import androidx.core.util.Predicate import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.util.toMatrixItem diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt index 14af10f6bd..b84f870f34 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt @@ -32,7 +32,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.RoomSummary import reactivecircus.flowbinding.appcompat.queryTextChanges -import java.util.concurrent.TimeUnit import javax.inject.Inject class SpaceLeaveAdvancedFragment @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt index 79fe4ea9f5..9bf304fa1c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt @@ -40,7 +40,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.RoomSummary import reactivecircus.flowbinding.appcompat.queryTextChanges -import java.util.concurrent.TimeUnit import javax.inject.Inject class SpaceAddRoomFragment @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsFragment.kt index 75b2878591..125686d200 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsFragment.kt @@ -44,7 +44,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import reactivecircus.flowbinding.appcompat.queryTextChanges -import java.util.concurrent.TimeUnit import javax.inject.Inject class SpaceManageRoomsFragment @Inject constructor( 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 8abc189def..c5cfed6974 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 @@ -42,7 +42,6 @@ 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 class SpacePeopleFragment @Inject constructor( 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 edcd02f639..bb3caebafa 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 @@ -47,7 +47,6 @@ 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 @@ -137,7 +136,7 @@ class UserListFragment @Inject constructor( private fun setupSearchView() { views.userListSearch .textChanges() - .onStart { emit(views.userListSearch.text)} + .onStart { emit(views.userListSearch.text) } .onEach { text -> val searchValue = text.trim() val action = if (searchValue.isBlank()) { diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index c7ffc43727..1187ace82e 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -28,12 +28,10 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.toggle import im.vector.app.core.platform.VectorViewModel -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt index 67b5090785..e883df9906 100644 --- a/vector/src/test/java/im/vector/app/test/Extensions.kt +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt @@ -36,17 +36,17 @@ class ViewModelTest( val viewEvents: FlowTestObserver ) { - fun assertEvents(vararg expected: VE): ViewModelTest { + fun assertEvents(vararg expected: VE): ViewModelTest { viewEvents.assertValues(*expected) return this } - fun assertState(expected: S): ViewModelTest { + fun assertState(expected: S): ViewModelTest { state() shouldBeEqualTo expected return this } - fun finish(){ + fun finish() { viewEvents.finish() } } From da47bfde2be855d073e463c19c75cdbf08a2c7c0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 27 Oct 2021 19:26:51 +0200 Subject: [PATCH 07/12] Flow migration: remove Shortcuts process from main thread --- .../src/main/java/im/vector/app/core/di/SingletonModule.kt | 2 +- .../im/vector/app/core/dispatchers/CoroutineDispatchers.kt | 4 +++- .../java/im/vector/app/features/home/ShortcutsHandler.kt | 5 +++++ .../im/vector/app/features/crypto/keys/KeysExporterTest.kt | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt index e89a060022..a3d8f39b30 100644 --- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt @@ -137,6 +137,6 @@ object VectorStaticModule { @Provides @JvmStatic fun providesCoroutineDispatchers(): CoroutineDispatchers { - return CoroutineDispatchers(io = Dispatchers.IO) + return CoroutineDispatchers(io = Dispatchers.IO, computation = Dispatchers.Default) } } diff --git a/vector/src/main/java/im/vector/app/core/dispatchers/CoroutineDispatchers.kt b/vector/src/main/java/im/vector/app/core/dispatchers/CoroutineDispatchers.kt index c489290a55..008ca4a9ce 100644 --- a/vector/src/main/java/im/vector/app/core/dispatchers/CoroutineDispatchers.kt +++ b/vector/src/main/java/im/vector/app/core/dispatchers/CoroutineDispatchers.kt @@ -19,4 +19,6 @@ package im.vector.app.core.dispatchers import kotlinx.coroutines.CoroutineDispatcher import javax.inject.Inject -data class CoroutineDispatchers @Inject constructor(val io: CoroutineDispatcher) +data class CoroutineDispatchers @Inject constructor( + val io: CoroutineDispatcher, + val computation: CoroutineDispatcher) diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index ff553577a0..f1f5bf6adf 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -23,8 +23,11 @@ import androidx.core.content.getSystemService import androidx.core.content.pm.ShortcutManagerCompat import androidx.lifecycle.asFlow import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.dispatchers.CoroutineDispatchers import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.RoomSortOrder @@ -34,6 +37,7 @@ import javax.inject.Inject class ShortcutsHandler @Inject constructor( private val context: Context, + private val appDispatchers: CoroutineDispatchers, private val shortcutCreator: ShortcutCreator, private val activeSessionHolder: ActiveSessionHolder ) { @@ -67,6 +71,7 @@ class ShortcutsHandler @Inject constructor( ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) } } + ?.flowOn(appDispatchers.computation) ?.launchIn(coroutineScope) ?: Job() } diff --git a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt index c75abf5db4..57ad2a52ab 100644 --- a/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt +++ b/vector/src/test/java/im/vector/app/features/crypto/keys/KeysExporterTest.kt @@ -42,7 +42,7 @@ class KeysExporterTest { private val keysExporter = KeysExporter( session = FakeSession(fakeCryptoService = cryptoService), context = context.instance, - dispatchers = CoroutineDispatchers(Dispatchers.Unconfined) + dispatchers = CoroutineDispatchers(Dispatchers.Unconfined, Dispatchers.Unconfined) ) @Before From 79c5af2585609b4fc07818f11ebeba32d6ae4206 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 28 Oct 2021 16:50:27 +0200 Subject: [PATCH 08/12] Flow migration: add Changelog --- changelog.d/4219.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4219.misc diff --git a/changelog.d/4219.misc b/changelog.d/4219.misc new file mode 100644 index 0000000000..69950e0915 --- /dev/null +++ b/changelog.d/4219.misc @@ -0,0 +1 @@ +Finish migration from RxJava to Flow \ No newline at end of file From edf068ee57137582fa1c9ee1bc96b0a8e7119a21 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 28 Oct 2021 17:36:29 +0200 Subject: [PATCH 09/12] Flow migration: use throttleFirst instead of sample on UI --- .../im/vector/app/core/flow/TimingOperators.kt | 18 ++++++++++++++++++ .../app/core/platform/VectorBaseActivity.kt | 4 ++-- .../VectorBaseBottomSheetDialogFragment.kt | 4 ++-- .../app/core/platform/VectorBaseFragment.kt | 4 ++-- .../quads/SharedSecuredStorageKeyFragment.kt | 4 ++-- .../SharedSecuredStoragePassphraseFragment.kt | 4 ++-- .../BootstrapConfirmPassphraseFragment.kt | 4 ++-- .../BootstrapEnterPassphraseFragment.kt | 4 ++-- .../recover/BootstrapMigrateBackupFragment.kt | 4 ++-- .../home/room/detail/RoomDetailFragment.kt | 4 ++-- .../spaces/preview/SpacePreviewFragment.kt | 4 ++-- 11 files changed, 38 insertions(+), 20 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/flow/TimingOperators.kt b/vector/src/main/java/im/vector/app/core/flow/TimingOperators.kt index edd77f6935..621a80d96e 100644 --- a/vector/src/main/java/im/vector/app/core/flow/TimingOperators.kt +++ b/vector/src/main/java/im/vector/app/core/flow/TimingOperators.kt @@ -67,6 +67,24 @@ fun Flow.chunk(durationInMillis: Long): Flow> { } } +@ExperimentalCoroutinesApi +fun Flow.throttleFirst(windowDuration: Long): Flow = flow { + var windowStartTime = System.currentTimeMillis() + var emitted = false + collect { value -> + val currentTime = System.currentTimeMillis() + val delta = currentTime - windowStartTime + if (delta >= windowDuration) { + windowStartTime += delta / windowDuration * windowDuration + emitted = false + } + if (!emitted) { + emit(value) + emitted = true + } + } +} + fun tickerFlow(scope: CoroutineScope, delayMillis: Long, initialDelayMillis: Long = delayMillis): Flow { return scope.fixedPeriodTicker(delayMillis, initialDelayMillis).consumeAsFlow() } 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 4f51317e2f..7fe939bef3 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 @@ -59,6 +59,7 @@ import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.restart import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.singletonEntryPoint +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.utils.toast import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs @@ -79,7 +80,6 @@ import im.vector.app.features.themes.ThemeUtils import im.vector.app.receivers.DebugReceiver 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 @@ -118,7 +118,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver protected fun View.debouncedClicks(onClicked: () -> Unit) { clicks() - .sample(300) + .throttleFirst(300) .onEach { onClicked() } .launchIn(lifecycleScope) } 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 1304c31177..20697c6d11 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 @@ -35,10 +35,10 @@ import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import dagger.hilt.android.EntryPointAccessors import im.vector.app.core.di.ActivityEntryPoint +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.utils.DimensionConverter 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 @@ -168,7 +168,7 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe protected fun View.debouncedClicks(onClicked: () -> Unit) { clicks() - .sample(300) + .throttleFirst(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 6f0a944b32..f4e1fe84e1 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 @@ -42,11 +42,11 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.toMvRxBundle +import im.vector.app.core.flow.throttleFirst import im.vector.app.features.navigation.Navigator import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog 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 @@ -239,7 +239,7 @@ abstract class VectorBaseFragment : Fragment(), MavericksView protected fun View.debouncedClicks(onClicked: () -> Unit) { clicks() - .sample(300) + .throttleFirst(300) .onEach { onClicked() } .launchIn(viewLifecycleOwner.lifecycleScope) } 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 edeb2f875d..c49291d6a2 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 @@ -26,12 +26,12 @@ import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.startImportTextFromFileIntent import im.vector.app.databinding.FragmentSsssAccessFromKeyBinding 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 @@ -50,7 +50,7 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment views.ssssRestoreWithKeyText.text = getString(R.string.enter_secret_storage_input_key) views.ssssKeyEnterEdittext.editorActionEvents() - .sample(300) + .throttleFirst(300) .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() 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 ee7a1ec305..c93e562d77 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 @@ -25,12 +25,12 @@ import androidx.core.text.toSpannable import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import im.vector.app.R +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding 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 javax.inject.Inject @@ -62,7 +62,7 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor( // .colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) views.ssssPassphraseEnterEdittext.editorActionEvents() - .sample(300) + .throttleFirst(300) .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() 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 a3580db996..940a4d9af3 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 @@ -27,11 +27,11 @@ import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding 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 javax.inject.Inject @@ -60,7 +60,7 @@ class BootstrapConfirmPassphraseFragment @Inject constructor() : } views.ssssPassphraseEnterEdittext.editorActionEvents() - .sample(300) + .throttleFirst(300) .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() 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 6fbd003017..77fb5ab3a6 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 @@ -25,12 +25,12 @@ import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.R +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding import im.vector.app.features.settings.VectorLocale 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 javax.inject.Inject @@ -55,7 +55,7 @@ class BootstrapEnterPassphraseFragment @Inject constructor() : views.ssssPassphraseEnterEdittext.setText(it.passphrase ?: "") } views.ssssPassphraseEnterEdittext.editorActionEvents() - .sample(300) + .throttleFirst(300) .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() 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 5cc4cfd0c2..5d0f3bbeae 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 @@ -33,6 +33,7 @@ import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider import im.vector.app.core.utils.colorizeMatchingText @@ -40,7 +41,6 @@ import im.vector.app.core.utils.startImportTextFromFileIntent import im.vector.app.databinding.FragmentBootstrapMigrateBackupBinding 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 @@ -65,7 +65,7 @@ class BootstrapMigrateBackupFragment @Inject constructor( views.bootstrapMigrateEditText.setText(it.passphrase ?: "") } views.bootstrapMigrateEditText.editorActionEvents() - .sample(300) + .throttleFirst(300) .onEach { if (it.actionId == EditorInfo.IME_ACTION_DONE) { submit() 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 1cd1221ac0..e855852f10 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 @@ -182,10 +182,10 @@ import im.vector.app.features.widgets.WidgetActivity 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.debounce 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 @@ -1350,7 +1350,7 @@ class RoomDetailFragment @Inject constructor( private fun observerUserTyping() { views.composerLayout.views.composerEditText.textChanges() .skipInitialValue() - .sample(300) + .debounce(300) .map { it.isNotEmpty() } .onEach { Timber.d("Typing: User is typing: $it") 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 8d3d623525..4d0d301721 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 @@ -32,6 +32,7 @@ import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSpacePreviewBinding import im.vector.app.features.home.AvatarRenderer @@ -39,7 +40,6 @@ import im.vector.app.features.spaces.SpacePreviewSharedAction import im.vector.app.features.spaces.SpacePreviewSharedActionViewModel 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 reactivecircus.flowbinding.appcompat.navigationClicks @@ -76,7 +76,7 @@ class SpacePreviewFragment @Inject constructor( views.roomPreviewNoPreviewToolbar .navigationClicks() - .sample(300) + .throttleFirst(300) .onEach { sharedActionViewModel.post(SpacePreviewSharedAction.DismissAction) } .launchIn(viewLifecycleOwner.lifecycleScope) From 16e4a7f6536d9f582d93f5bcf31521722946db00 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 28 Oct 2021 18:17:01 +0200 Subject: [PATCH 10/12] Flow migration: fix kotlinx-coroutines-debug dependencie --- vector/build.gradle | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/vector/build.gradle b/vector/build.gradle index c32e70754a..85ae4a48fc 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -105,7 +105,6 @@ def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0 android { - // Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use // Ref: https://issuetracker.google.com/issues/144111441 ndkVersion "21.3.6528147" @@ -463,7 +462,7 @@ dependencies { gplayImplementation 'com.google.android.gms:play-services-oss-licenses:17.0.0' implementation "androidx.emoji:emoji-appcompat:1.1.0" - implementation ('com.github.BillCarsonFr:JsonViewer:0.7') + implementation('com.github.BillCarsonFr:JsonViewer:0.7') // WebRTC // org.webrtc:google-webrtc is for development purposes only @@ -504,7 +503,9 @@ dependencies { // Plant Timber tree for test testImplementation libs.tests.timberJunitRule testImplementation libs.airbnb.mavericksTesting - testImplementation libs.jetbrains.coroutinesTest + testImplementation(libs.jetbrains.coroutinesTest) { + exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug" + } // Activate when you want to check for leaks, from time to time. //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3' @@ -518,7 +519,9 @@ dependencies { androidTestImplementation libs.androidx.espressoIntents androidTestImplementation libs.tests.kluent androidTestImplementation libs.androidx.coreTesting - androidTestImplementation libs.jetbrains.coroutinesTest + androidTestImplementation(libs.jetbrains.coroutinesTest) { + exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug" + } // Plant Timber tree for test androidTestImplementation libs.tests.timberJunitRule // "The one who serves a great Espresso" From bb4a820c3159e2dc5537d143ab3bbe48a07d6858 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 28 Oct 2021 19:19:59 +0200 Subject: [PATCH 11/12] Flow migration: update after PR reviews --- docs/rx_flow_migration.md | 41 +++++++++++++++++++ .../matrix/android/sdk/flow/FlowSession.kt | 7 ++-- .../java/im/vector/app/VectorApplication.kt | 3 -- .../java/im/vector/app/core/rx/RxConfig.kt | 28 ------------- .../app/features/home/HomeDetailViewModel.kt | 4 +- .../app/features/home/ShortcutsHandler.kt | 5 +-- .../home/UnreadMessagesSharedViewModel.kt | 6 +-- .../quads/SharedSecureStorageViewModelTest.kt | 4 -- .../java/im/vector/app/test/InstantRxRule.kt | 32 --------------- 9 files changed, 52 insertions(+), 78 deletions(-) create mode 100644 docs/rx_flow_migration.md delete mode 100644 vector/src/main/java/im/vector/app/core/rx/RxConfig.kt delete mode 100644 vector/src/test/java/im/vector/app/test/InstantRxRule.kt diff --git a/docs/rx_flow_migration.md b/docs/rx_flow_migration.md new file mode 100644 index 0000000000..a438b0f6fb --- /dev/null +++ b/docs/rx_flow_migration.md @@ -0,0 +1,41 @@ +Useful links: +- https://github.com/ReactiveCircus/FlowBinding +- https://ivanisidrowu.github.io/kotlin/2020/08/09/Kotlin-Flow-Migration-And-Testing.html + + +Rx is now completely removed from Element dependencies. +Some examples of the changes: + +``` + sharedActionViewModel + .observe() + .subscribe { handleQuickActions(it) } + .disposeOnDestroyView() + ``` + +became + + ``` + sharedActionViewModel + .stream() + .onEach { handleQuickActions(it) } + .launchIn(viewLifecycleOwner.lifecycleScope) + +``` + +Inside fragment use +``` +launchIn(viewLifecycleOwner.lifecycleScope) +``` +Inside activity use +``` +launchIn(lifecycleScope) +``` +Inside viewModel use +``` +launchIn(viewModelScope) +``` + +Also be aware that when using these scopes the coroutine is launched on Dispatchers.Main by default. + + diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt index 13fd097bcd..2a0abd3d24 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt @@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.pushers.Pusher +import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState @@ -44,10 +45,10 @@ import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo class FlowSession(private val session: Session) { - fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Flow> { - return session.getRoomSummariesLive(queryParams).asFlow() + fun liveRoomSummaries(queryParams: RoomSummaryQueryParams, sortOrder: RoomSortOrder = RoomSortOrder.NONE): Flow> { + return session.getRoomSummariesLive(queryParams, sortOrder).asFlow() .startWith(session.coroutineDispatchers.io) { - session.getRoomSummaries(queryParams) + session.getRoomSummaries(queryParams, sortOrder) } } diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index d9027231da..80b397231b 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -43,7 +43,6 @@ import dagger.hilt.android.HiltAndroidApp import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.configureAndStart import im.vector.app.core.extensions.startSyncing -import im.vector.app.core.rx.RxConfig import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.configuration.VectorConfiguration import im.vector.app.features.disclaimer.doNotShowDisclaimerDialog @@ -93,7 +92,6 @@ class VectorApplication : @Inject lateinit var versionProvider: VersionProvider @Inject lateinit var notificationUtils: NotificationUtils @Inject lateinit var appStateHandler: AppStateHandler - @Inject lateinit var rxConfig: RxConfig @Inject lateinit var popupAlertManager: PopupAlertManager @Inject lateinit var pinLocker: PinLocker @Inject lateinit var callManager: WebRtcCallManager @@ -118,7 +116,6 @@ class VectorApplication : appContext = this invitesAcceptor.initialize() vectorUncaughtExceptionHandler.activate(this) - rxConfig.setupRxPlugin() // Remove Log handler statically added by Jitsi Timber.forest() 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 deleted file mode 100644 index 066839319e..0000000000 --- a/vector/src/main/java/im/vector/app/core/rx/RxConfig.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.core.rx - -import javax.inject.Inject - -class RxConfig @Inject constructor() { - - /** - * Make sure unhandled Rx error does not crash the app in production - */ - fun setupRxPlugin() { - } -} 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 b110005c31..1c1d012cc8 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 @@ -27,6 +27,7 @@ import im.vector.app.RoomGroupingMethod import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.singletonEntryPoint +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.call.dialpad.DialPadLookup import im.vector.app.features.call.lookup.CallProtocolsChecker @@ -43,7 +44,6 @@ import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter @@ -214,7 +214,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho sortOrder = RoomSortOrder.NONE ).asFlow() } - .sample(300) + .throttleFirst(300) .onEach { when (val groupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) { is RoomGroupingMethod.ByLegacyGroup -> { diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index a1848cad31..a84a721a31 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -21,7 +21,6 @@ import android.content.pm.ShortcutManager import android.os.Build import androidx.core.content.getSystemService import androidx.core.content.pm.ShortcutManagerCompat -import androidx.lifecycle.asFlow import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.dispatchers.CoroutineDispatchers import im.vector.app.features.pin.PinCodeStore @@ -37,6 +36,7 @@ import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject @@ -61,13 +61,12 @@ class ShortcutsHandler @Inject constructor( } hasPinCode.set(pinCodeStore.getEncodedPin() != null) val session = activeSessionHolder.getSafeActiveSession() ?: return Job() - return session.getRoomSummariesLive( + return session.flow().liveRoomSummaries( roomSummaryQueryParams { memberships = listOf(Membership.JOIN) }, sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY ) - .asFlow() .onStart { pinCodeStore.addListener(this@ShortcutsHandler) } .onCompletion { pinCodeStore.removeListener(this@ShortcutsHandler) } .onEach { rooms -> diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index 7091dc3f00..6c0ae71cfa 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -26,6 +26,7 @@ import im.vector.app.AppStateHandler import im.vector.app.RoomGroupingMethod import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel @@ -36,7 +37,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.RoomSortOrder @@ -79,7 +79,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) }, sortOrder = RoomSortOrder.NONE ).asFlow() - .sample(300) + .throttleFirst(300) .execute { val counts = session.getNotificationCountForRooms( roomSummaryQueryParams { @@ -114,7 +114,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia this.memberships = Membership.activeMemberships() }, sortOrder = RoomSortOrder.NONE ).asFlow() - .sample(300) + .throttleFirst(300) } ) { groupingMethod, _ -> when (groupingMethod.orNull()) { diff --git a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt index 00828acbb8..ea9335aaff 100644 --- a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt @@ -18,7 +18,6 @@ package im.vector.app.features.crypto.quads import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.test.MvRxTestRule -import im.vector.app.test.InstantRxRule import im.vector.app.test.fakes.FakeSession import im.vector.app.test.fakes.FakeStringProvider import im.vector.app.test.test @@ -40,9 +39,6 @@ private val KEY_INFO_WITHOUT_PASSPHRASE = KeyInfo(id = "id", content = SecretSto class SharedSecureStorageViewModelTest { - @get:Rule - val instantRx = InstantRxRule() - @get:Rule val mvrxTestRule = MvRxTestRule() diff --git a/vector/src/test/java/im/vector/app/test/InstantRxRule.kt b/vector/src/test/java/im/vector/app/test/InstantRxRule.kt deleted file mode 100644 index 1145cb7dd1..0000000000 --- a/vector/src/test/java/im/vector/app/test/InstantRxRule.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.test - -import io.reactivex.android.plugins.RxAndroidPlugins -import io.reactivex.plugins.RxJavaPlugins -import io.reactivex.schedulers.Schedulers -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement - -class InstantRxRule : TestRule { - override fun apply(base: Statement, description: Description?): Statement { - RxJavaPlugins.setInitNewThreadSchedulerHandler { Schedulers.trampoline() } - RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() } - return base - } -} From e43bfaebc6843341dcca7c6d9c7d6e675a0d39a9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 2 Nov 2021 11:50:56 +0100 Subject: [PATCH 12/12] Flow migration: more update after PR reviews --- .../src/main/assets/open_source_licenses.html | 20 ++----------------- .../reactions/EmojiReactionPickerActivity.kt | 4 ++-- .../settings/devices/DevicesViewModel.kt | 3 ++- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index 19daf3359b..529b7da2f1 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -279,25 +279,9 @@ SOFTWARE. Copyright 2012 The Dagger Authors
  • - rxkotlin + FlowBinding
    - Copyright io.reactivex. -
  • -
  • - rxandroid -
    - Copyright io.reactivex. -
  • -
  • - rxrelay -
    - Copyright 2014 Netflix, Inc. - Copyright 2015 Jake Wharton -
  • -
  • - rxbinding -
    - Copyright (C) 2015 Jake Wharton + Copyright 2019 Yang Chen
  • Epoxy 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 3aeab7c876..d377c74ad7 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 @@ -32,12 +32,12 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.EmojiCompatFontProvider import im.vector.app.R import im.vector.app.core.extensions.observeEvent +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityEmojiReactionPickerBinding import im.vector.app.features.reactions.data.EmojiDataSource 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 javax.inject.Inject @@ -167,7 +167,7 @@ class EmojiReactionPickerActivity : VectorBaseActivity onQueryText(query.toString()) } 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 362237e60d..67ed2e18f2 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 @@ -29,6 +29,7 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.flow.throttleFirst import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.PublishDataSource @@ -165,7 +166,7 @@ class DevicesViewModel @AssistedInject constructor( // ) // } - refreshSource.stream().sample(4_000) + refreshSource.stream().throttleFirst(4_000) .onEach { session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) session.cryptoService().downloadKeys(listOf(session.myUserId), true, NoOpMatrixCallback())