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 01eb6e3c31..0f50b82aa8 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 @@ -16,7 +16,6 @@ package im.vector.app.features.home -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -47,6 +46,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.roomSummaryQueryParams import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.rx import timber.log.Timber @@ -95,7 +95,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho updateShowDialPadTab() observeDataStore() callManager.addProtocolsCheckerListener(this) - session.rx().liveUser(session.myUserId).execute { + session.flow().liveUser(session.myUserId).execute { copy( myMatrixItem = it.invoke()?.getOrNull()?.toMatrixItem() ) diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt index db3317a214..143f843954 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt @@ -25,25 +25,25 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.settings.VectorPreferences -import io.reactivex.Observable +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.MatrixItem -import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo -import org.matrix.android.sdk.rx.rx import timber.log.Timber -import java.util.concurrent.TimeUnit data class UnknownDevicesState( val myMatrixItem: MatrixItem.UserItem? = null, @@ -98,31 +98,31 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted } ) - Observable.combineLatest, List, Optional, List>( - session.rx().liveUserCryptoDevices(session.myUserId), - session.rx().liveMyDevicesInfo(), - session.rx().liveCrossSigningPrivateKeys(), - { cryptoList, infoList, pInfo -> - // Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}") -// Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}") - infoList - .filter { info -> - // filter verified session, by checking the crypto device info - cryptoList.firstOrNull { info.deviceId == it.deviceId }?.isVerified?.not().orFalse() - } - // filter out ignored devices - .filter { !ignoredDeviceList.contains(it.deviceId) } - .sortedByDescending { it.lastSeenTs } - .map { deviceInfo -> - val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0 - DeviceDetectionInfo( - deviceInfo, - deviceKnownSince > currentSessionTs + 60_000, // short window to avoid false positive, - pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change - ) - } - } + combine( + session.flow().liveUserCryptoDevices(session.myUserId), + session.flow().liveMyDevicesInfo(), + session.flow().liveCrossSigningPrivateKeys() ) + { cryptoList, infoList, pInfo -> + // Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}") +// Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}") + infoList + .filter { info -> + // filter verified session, by checking the crypto device info + cryptoList.firstOrNull { info.deviceId == it.deviceId }?.isVerified?.not().orFalse() + } + // filter out ignored devices + .filter { !ignoredDeviceList.contains(it.deviceId) } + .sortedByDescending { it.lastSeenTs } + .map { deviceInfo -> + val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0 + DeviceDetectionInfo( + deviceInfo, + deviceKnownSince > currentSessionTs + 60_000, // short window to avoid false positive, + pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change + ) + } + } .distinctUntilChanged() .execute { async -> // Timber.v("## Detector trigger passed distinct") @@ -132,14 +132,14 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted ) } - session.rx().liveUserCryptoDevices(session.myUserId) + session.flow().liveUserCryptoDevices(session.myUserId) .distinctUntilChanged() - .throttleLast(5_000, TimeUnit.MILLISECONDS) - .subscribe { + .sample(5_000) + .onEach { // If we have a new crypto device change, we might want to trigger refresh of device info session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) } - .disposeOnClear() + .launchIn(viewModelScope) // trigger a refresh of lastSeen / last Ip session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) 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 1f6fec0410..ddb1c51b5b 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 @@ -28,7 +28,6 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import com.jakewharton.rxrelay2.BehaviorRelay -import com.jakewharton.rxrelay2.PublishRelay import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -60,11 +59,11 @@ 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.Observable import io.reactivex.rxkotlin.subscribeBy -import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn @@ -112,8 +111,6 @@ import org.matrix.android.sdk.api.util.toOptional 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 org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap import timber.log.Timber import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean @@ -143,7 +140,7 @@ class RoomDetailViewModel @AssistedInject constructor( private val eventId = initialState.eventId private val invisibleEventsObservable = BehaviorRelay.create() private val visibleEventsObservable = BehaviorRelay.create() - private var timelineEvents = PublishRelay.create>() + private var timelineEvents = MutableSharedFlow>(0) val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId) // Same lifecycle than the ViewModel (survive to screen rotation) @@ -1533,14 +1530,12 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun getUnreadState() { - Observable - .combineLatest, RoomSummary, UnreadState>( - timelineEvents.observeOn(Schedulers.computation()), - room.rx().liveRoomSummary().unwrap(), - { timelineEvents, roomSummary -> - computeUnreadState(timelineEvents, roomSummary) - } - ) + combine( + timelineEvents, + room.flow().liveRoomSummary().unwrap() + ) { timelineEvents, roomSummary -> + computeUnreadState(timelineEvents, roomSummary) + } // We don't want live update of unread so we skip when we already had a HasUnread or HasNoUnread .distinctUntilChanged { previous, current -> when { @@ -1549,10 +1544,9 @@ class RoomDetailViewModel @AssistedInject constructor( else -> false } } - .subscribe { - setState { copy(unreadState = it) } + .setOnEach { + copy(unreadState = it) } - .disposeOnClear() } private fun computeUnreadState(events: List, roomSummary: RoomSummary): UnreadState { @@ -1619,7 +1613,7 @@ class RoomDetailViewModel @AssistedInject constructor( } override fun onTimelineUpdated(snapshot: List) { - timelineEvents.accept(snapshot) + timelineEvents.tryEmit(snapshot) // PreviewUrl if (vectorPreferences.showUrlPreviews()) { diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index ab5337d7cd..14624b800e 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -25,20 +25,17 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.R 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.features.powerlevel.PowerLevelsFlowFactory -import io.reactivex.Observable -import io.reactivex.functions.BiFunction import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.combineLatest import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -50,8 +47,6 @@ import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role @@ -60,8 +55,6 @@ import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomMemberProfileViewState, private val stringProvider: StringProvider, @@ -114,7 +107,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } } - session.rx().liveUserCryptoDevices(initialState.userId) + session.flow().liveUserCryptoDevices(initialState.userId) .map { Pair( it.fold(true, { prev, dev -> prev && dev.isVerified }), @@ -128,14 +121,14 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v ) } - session.rx().liveCrossSigningInfo(initialState.userId) + session.flow().liveCrossSigningInfo(initialState.userId) .execute { copy(userMXCrossSigningInfo = it.invoke()?.getOrNull()) } } private fun observeIgnoredState() { - session.rx().liveIgnoredUsers() + session.flow().liveIgnoredUsers() .map { ignored -> ignored.find { it.userId == initialState.userId @@ -252,7 +245,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v val queryParams = roomMemberQueryParams { this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE) } - room.rx().liveRoomMembers(queryParams) + room.flow().liveRoomMembers(queryParams) .map { it.firstOrNull().toOptional() } .unwrap() .execute { @@ -312,7 +305,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v roomSummaryLive.execute { copy(isRoomEncrypted = it.invoke()?.isEncrypted == true) } - roomSummaryLive.combine(powerLevelsContentLive){roomSummary, powerLevelsContent -> + roomSummaryLive.combine(powerLevelsContentLive) { roomSummary, powerLevelsContent -> val roomName = roomSummary.toMatrixItem().getBestName() val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) when (val userPowerLevel = powerLevelsHelper.getUserRole(initialState.userId)) { diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index 2baf27e694..b638d84181 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.rx.rx @@ -55,14 +56,14 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva } init { - session.rx().liveUserCryptoDevices(args.userId) + session.flow().liveUserCryptoDevices(args.userId) .execute { copy(cryptoDevices = it).also { refreshSelectedId() } } - session.rx().liveCrossSigningInfo(args.userId) + session.flow().liveCrossSigningInfo(args.userId) .execute { copy(memberCrossSigningKey = it.invoke()?.getOrNull()) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index cb26d6407b..28a1804fab 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -131,7 +131,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } private fun observeRoomSummary() { - room.rx().liveRoomSummary() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index b92d1a4bd3..813d50c6bb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -38,6 +38,8 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap @@ -54,15 +56,14 @@ class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initia private val room = session.getRoom(initialState.roomId)!! init { - val rxRoom = room.rx() - room.rx().liveRoomSummary() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy(roomSummary = async) } - rxRoom.liveRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.BAN) }) + room.flow().liveRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.BAN) }) .execute { copy( bannedMemberSummaries = it 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 cd6d6f1610..2873b20400 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 @@ -16,6 +16,7 @@ package im.vector.app.features.roomprofile.members +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory @@ -27,10 +28,16 @@ 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 io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers +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 +import kotlinx.coroutines.flow.switchMap import kotlinx.coroutines.launch import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.extensions.orFalse @@ -44,10 +51,9 @@ import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role -import org.matrix.android.sdk.rx.asObservable -import org.matrix.android.sdk.rx.mapOptional -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.mapOptional +import org.matrix.android.sdk.flow.unwrap import timber.log.Timber class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState, @@ -87,28 +93,28 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState memberships = Membership.activeMemberships() } - Observable - .combineLatest, PowerLevelsContent, RoomMemberSummaries>( - room.rx().liveRoomMembers(roomMemberQueryParams), - room.rx() - .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) - .mapOptional { it.content.toModel() } - .unwrap(), - { roomMembers, powerLevelsContent -> - buildRoomMemberSummaries(powerLevelsContent, roomMembers) - } - ) + combine( + room.flow().liveRoomMembers(roomMemberQueryParams), + room.flow() + .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + .mapOptional { it.content.toModel() } + .unwrap() + ) + { roomMembers, powerLevelsContent -> + buildRoomMemberSummaries(powerLevelsContent, roomMembers) + } + .execute { async -> copy(roomMemberSummaries = async) } if (room.isEncrypted()) { - room.rx().liveRoomMembers(roomMemberQueryParams) - .observeOn(AndroidSchedulers.mainThread()) - .switchMap { membersSummary -> + room.flow().liveRoomMembers(roomMemberQueryParams) + .flowOn(Dispatchers.Main) + .flatMapLatest { membersSummary -> session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId }) - .asObservable() - .doOnError { Timber.e(it) } + .asFlow() + .catch { Timber.e(it) } .map { deviceList -> // If any key change, emit the userIds list deviceList.groupBy { it.userId }.mapValues { @@ -147,7 +153,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } private fun observeRoomSummary() { - room.rx().liveRoomSummary() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy(roomSummary = async) @@ -155,7 +161,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } private fun observeThirdPartyInvites() { - room.rx().liveStateEvents(setOf(EventType.STATE_ROOM_THIRD_PARTY_INVITE)) + room.flow().liveStateEvents(setOf(EventType.STATE_ROOM_THIRD_PARTY_INVITE)) .execute { async -> copy(threePidInvites = async) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt index 51cd4a4ecc..d944b77f7d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt @@ -28,6 +28,8 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap @@ -64,7 +66,7 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor( } private fun observeSummary() { - room.rx().liveRoomSummary() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy(roomSummary = async) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index 2ae27f7799..4526024143 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.roomprofile.uploads -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -25,15 +24,15 @@ import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.message.MessageType -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap class RoomUploadsViewModel @AssistedInject constructor( @Assisted initialState: RoomUploadsViewState, @@ -66,7 +65,7 @@ class RoomUploadsViewModel @AssistedInject constructor( } private fun observeRoomSummary() { - room.rx().liveRoomSummary() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy(roomSummary = async) diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index 6f1058ec7c..a8fafb096a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -15,7 +15,6 @@ */ package im.vector.app.features.settings.crosssigning -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -28,8 +27,8 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.login.ReAuthHelper -import io.reactivex.Observable import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor @@ -38,14 +37,11 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo -import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified 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 org.matrix.android.sdk.rx.rx import timber.log.Timber import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -59,26 +55,26 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( ) : VectorViewModel(initialState) { init { - Observable.combineLatest, Optional, Pair, Optional>>( - session.rx().liveMyDevicesInfo(), - session.rx().liveCrossSigningInfo(session.myUserId), - { myDevicesInfo, mxCrossSigningInfo -> - myDevicesInfo to mxCrossSigningInfo - } + combine( + session.flow().liveMyDevicesInfo(), + session.flow().liveCrossSigningInfo(session.myUserId) ) - .execute { data -> - val crossSigningKeys = data.invoke()?.second?.getOrNull() - val xSigningIsEnableInAccount = crossSigningKeys != null - val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified() - val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign() + { myDevicesInfo, mxCrossSigningInfo -> + myDevicesInfo to mxCrossSigningInfo + } + .execute { data -> + val crossSigningKeys = data.invoke()?.second?.getOrNull() + val xSigningIsEnableInAccount = crossSigningKeys != null + val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified() + val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign() - copy( - crossSigningInfo = crossSigningKeys, - xSigningIsEnableInAccount = xSigningIsEnableInAccount, - xSigningKeysAreTrusted = xSigningKeysAreTrusted, - xSigningKeyCanSign = xSigningKeyCanSign - ) - } + copy( + crossSigningInfo = crossSigningKeys, + xSigningIsEnableInAccount = xSigningIsEnableInAccount, + xSigningKeysAreTrusted = xSigningKeysAreTrusted, + xSigningKeyCanSign = xSigningKeyCanSign + ) + } } var uiaContinuation: Continuation? = null @@ -126,7 +122,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( } Unit } - is CrossSigningSettingsAction.SsoAuthDone -> { + is CrossSigningSettingsAction.SsoAuthDone -> { Timber.d("## UIA - FallBack success") if (pendingAuth != null) { uiaContinuation?.resume(pendingAuth!!) @@ -134,7 +130,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( uiaContinuation?.resumeWithException(IllegalArgumentException()) } } - is CrossSigningSettingsAction.PasswordAuthDone -> { + is CrossSigningSettingsAction.PasswordAuthDone -> { val decryptedPass = session.loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) uiaContinuation?.resume( UserPasswordAuth( @@ -144,7 +140,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( ) ) } - CrossSigningSettingsAction.ReAuthCancelled -> { + CrossSigningSettingsAction.ReAuthCancelled -> { Timber.d("## UIA - Reauth cancelled") _viewEvents.post(CrossSigningSettingsViewEvents.HideModalWaitingView) uiaContinuation?.resumeWithException(Exception()) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt index 975236beb1..38342efc46 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt @@ -25,7 +25,9 @@ import dagger.assisted.AssistedFactory 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.map import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.rx.rx @@ -48,7 +50,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup() ) } - session.rx().liveCrossSigningInfo(session.myUserId) + session.flow().liveCrossSigningInfo(session.myUserId) .execute { copy( hasAccountCrossSigning = it.invoke()?.getOrNull() != null, @@ -56,7 +58,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As ) } - session.rx().liveUserCryptoDevices(session.myUserId) + session.flow().liveUserCryptoDevices(session.myUserId) .map { list -> list.firstOrNull { it.deviceId == deviceId } } @@ -67,7 +69,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As ) } - session.rx().liveUserCryptoDevices(session.myUserId) + session.flow().liveUserCryptoDevices(session.myUserId) .map { it.size } .execute { copy( @@ -79,7 +81,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As copy(deviceInfo = Loading()) } - session.rx().liveMyDevicesInfo() + session.flow().liveMyDevicesInfo() .map { devices -> devices.firstOrNull { it.deviceId == deviceId } ?: DeviceInfo(deviceId = deviceId) } 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 04e743b8df..b2b4c0c396 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 @@ -16,7 +16,6 @@ package im.vector.app.features.settings.devices -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -34,31 +33,36 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.login.ReAuthHelper -import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +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 org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.NoOpMatrixCallback +import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.data.LoginFlowTypes +import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse +import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState -import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse -import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo 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.api.auth.UIABaseAuth -import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.internal.util.awaitCallback -import org.matrix.android.sdk.rx.rx import timber.log.Timber import java.util.concurrent.TimeUnit import javax.net.ssl.HttpsURLConnection @@ -118,18 +122,18 @@ class DevicesViewModel @AssistedInject constructor( ) } - Observable.combineLatest, List, List>( - session.rx().liveUserCryptoDevices(session.myUserId), - session.rx().liveMyDevicesInfo(), - { cryptoList, infoList -> - infoList - .sortedByDescending { it.lastSeenTs } - .map { deviceInfo -> - val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId } - DeviceFullInfo(deviceInfo, cryptoDeviceInfo) - } - } + combine( + session.flow().liveUserCryptoDevices(session.myUserId), + session.flow().liveMyDevicesInfo() ) + { cryptoList, infoList -> + infoList + .sortedByDescending { it.lastSeenTs } + .map { deviceInfo -> + val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId } + DeviceFullInfo(deviceInfo, cryptoDeviceInfo) + } + } .distinctUntilChanged() .execute { async -> copy( @@ -137,7 +141,7 @@ class DevicesViewModel @AssistedInject constructor( ) } - session.rx().liveCrossSigningInfo(session.myUserId) + session.flow().liveCrossSigningInfo(session.myUserId) .execute { copy( hasAccountCrossSigning = it.invoke()?.getOrNull() != null, @@ -146,24 +150,24 @@ class DevicesViewModel @AssistedInject constructor( } session.cryptoService().verificationService().addListener(this) -// session.rx().liveMyDeviceInfo() +// session.flow().liveMyDeviceInfo() // .execute { // copy( // devices = it // ) // } - session.rx().liveUserCryptoDevices(session.myUserId) + session.flow().liveUserCryptoDevices(session.myUserId) .map { it.size } .distinctUntilChanged() - .throttleLast(5_000, TimeUnit.MILLISECONDS) - .subscribe { + .sample(5_000) + .onEach { // If we have a new crypto device change, we might want to trigger refresh of device info session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) } - .disposeOnClear() + .launchIn(viewModelScope) -// session.rx().liveUserCryptoDevices(session.myUserId) +// session.flow().liveUserCryptoDevices(session.myUserId) // .execute { // copy( // cryptoDevices = it diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt index 4d94389850..cd0d74a288 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.settings.threepids -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -33,15 +32,15 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.ReadOnceTrue import im.vector.app.features.auth.ReAuthActivity import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth -import org.matrix.android.sdk.api.auth.UIABaseAuth -import org.matrix.android.sdk.api.auth.UserPasswordAuth -import org.matrix.android.sdk.rx.rx import timber.log.Timber import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -102,7 +101,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( } private fun observeThreePids() { - session.rx() + session.flow() .liveThreePIds(true) .execute { copy( @@ -112,7 +111,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( } private fun observePendingThreePids() { - session.rx() + session.flow() .livePendingThreePIds() .execute { copy( @@ -131,13 +130,13 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( override fun handle(action: ThreePidsSettingsAction) { when (action) { - is ThreePidsSettingsAction.AddThreePid -> handleAddThreePid(action) + is ThreePidsSettingsAction.AddThreePid -> handleAddThreePid(action) is ThreePidsSettingsAction.ContinueThreePid -> handleContinueThreePid(action) - is ThreePidsSettingsAction.SubmitCode -> handleSubmitCode(action) - is ThreePidsSettingsAction.CancelThreePid -> handleCancelThreePid(action) - is ThreePidsSettingsAction.DeleteThreePid -> handleDeleteThreePid(action) - is ThreePidsSettingsAction.ChangeUiState -> handleChangeUiState(action) - ThreePidsSettingsAction.SsoAuthDone -> { + is ThreePidsSettingsAction.SubmitCode -> handleSubmitCode(action) + is ThreePidsSettingsAction.CancelThreePid -> handleCancelThreePid(action) + is ThreePidsSettingsAction.DeleteThreePid -> handleDeleteThreePid(action) + is ThreePidsSettingsAction.ChangeUiState -> handleChangeUiState(action) + ThreePidsSettingsAction.SsoAuthDone -> { Timber.d("## UIA - FallBack success") if (pendingAuth != null) { uiaContinuation?.resume(pendingAuth!!) @@ -155,7 +154,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( ) ) } - ThreePidsSettingsAction.ReAuthCancelled -> { + ThreePidsSettingsAction.ReAuthCancelled -> { Timber.d("## UIA - Reauth cancelled") uiaContinuation?.resumeWithException(Exception()) uiaContinuation = null diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt index 665d3b28fa..5fdd10e742 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt @@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.rx import java.util.concurrent.TimeUnit @@ -68,7 +69,7 @@ class IncomingShareViewModel @AssistedInject constructor( memberships = listOf(Membership.JOIN) } session - .rx().liveRoomSummaries(queryParams) + .flow().liveRoomSummaries(queryParams) .execute { copy(roomSummaries = it) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt index ad764363db..bb30670da9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt @@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.rx import timber.log.Timber @@ -77,7 +78,7 @@ class SpaceMenuViewModel @AssistedInject constructor( session.getRoom(initialState.spaceId)?.let { room -> - room.rx().liveRoomSummary().subscribe { + room.flow().liveRoomSummary().onEach { it.getOrNull()?.let { if (it.membership == Membership.LEAVE) { setState { copy(leavingState = Success(Unit)) } @@ -87,7 +88,7 @@ class SpaceMenuViewModel @AssistedInject constructor( } } } - }.disposeOnClear() + }.launchIn(viewModelScope) PowerLevelsFlowFactory(room) .createFlow() diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt index 0daa2522e9..3d24cf6225 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.spaces.leave -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -31,6 +30,8 @@ import dagger.assisted.AssistedInject import im.vector.app.AppStateHandler import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import okhttp3.internal.toImmutableList import org.matrix.android.sdk.api.query.ActiveSpaceFilter @@ -38,7 +39,8 @@ import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.Session 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.rx +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import timber.log.Timber class SpaceLeaveAdvancedViewModel @AssistedInject constructor( @@ -95,17 +97,17 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( val spaceSummary = session.getRoomSummary(initialState.spaceId) setState { copy(spaceSummary = spaceSummary) } session.getRoom(initialState.spaceId)?.let { room -> - room.rx().liveRoomSummary().subscribe { - it.getOrNull()?.let { - if (it.membership == Membership.LEAVE) { - setState { copy(leaveState = Success(Unit)) } - if (appStateHandler.safeActiveSpaceId() == initialState.spaceId) { - // switch to home? - appStateHandler.setCurrentSpace(null, session) + room.flow().liveRoomSummary() + .unwrap() + .onEach { + if (it.membership == Membership.LEAVE) { + setState { copy(leaveState = Success(Unit)) } + if (appStateHandler.safeActiveSpaceId() == initialState.spaceId) { + // switch to home? + appStateHandler.setCurrentSpace(null, session) + } } - } - } - } + }.launchIn(viewModelScope) } viewModelScope.launch { diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index 44d246885a..f88bf6ef56 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.widgets import android.net.Uri -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -26,11 +25,12 @@ import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.widgets.permissions.WidgetPermissionsHelper +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -41,9 +41,10 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure -import org.matrix.android.sdk.rx.mapOptional +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.mapOptional +import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap import timber.log.Timber import javax.net.ssl.HttpsURLConnection @@ -118,16 +119,15 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi if (room == null) { return } - room.rx().liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + room.flow().liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() .map { PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, true, null) } - .subscribe { - setState { copy(canManageWidgets = it) } + .setOnEach { + copy(canManageWidgets = it) } - .disposeOnClear() } private fun observeWidgetIfNeeded() { diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt index 0076bf580e..c3719ffd8e 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt @@ -25,27 +25,23 @@ import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import io.reactivex.Observable -import io.reactivex.functions.Function4 -import io.reactivex.subjects.PublishSubject +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME -import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener -import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo -import org.matrix.android.sdk.rx.rx -import java.util.concurrent.TimeUnit +import org.matrix.android.sdk.flow.flow data class ServerBackupStatusViewState( val bannerState: Async = Uninitialized @@ -91,43 +87,38 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS val keysExportedToFile = MutableLiveData() val keysBackupState = MutableLiveData() - private val keyBackupPublishSubject: PublishSubject = PublishSubject.create() + private val keyBackupFlow = MutableSharedFlow(0) init { session.cryptoService().keysBackupService().addListener(this) - keysBackupState.value = session.cryptoService().keysBackupService().state - - Observable.combineLatest, Optional, KeysBackupState, Optional, BannerState>( - session.rx().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)), - session.rx().liveCrossSigningInfo(session.myUserId), - keyBackupPublishSubject, - session.rx().liveCrossSigningPrivateKeys(), - Function4 { _, crossSigningInfo, keyBackupState, pInfo -> - // first check if 4S is already setup - if (session.sharedSecretStorageService.isRecoverySetup()) { - // 4S is already setup sp we should not display anything - return@Function4 when (keyBackupState) { - KeysBackupState.BackingUp -> BannerState.BackingUp - else -> BannerState.Hidden - } - } - - // So recovery is not setup - // Check if cross signing is enabled and local secrets known - if ( - crossSigningInfo.getOrNull() == null - || (crossSigningInfo.getOrNull()?.isTrusted() == true - && pInfo.getOrNull()?.allKnown().orFalse()) - ) { - // So 4S is not setup and we have local secrets, - return@Function4 BannerState.Setup(numberOfKeys = getNumberOfKeysToBackup()) - } - - BannerState.Hidden + val liveUserAccountData = session.flow().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) + val liveCrossSigningInfo = session.flow().liveCrossSigningInfo(session.myUserId) + val liveCrossSigningPrivateKeys = session.flow().liveCrossSigningPrivateKeys() + combine(liveUserAccountData, liveCrossSigningInfo, keyBackupFlow, liveCrossSigningPrivateKeys) { _, crossSigningInfo, keyBackupState, pInfo -> + // first check if 4S is already setup + if (session.sharedSecretStorageService.isRecoverySetup()) { + // 4S is already setup sp we should not display anything + return@combine when (keyBackupState) { + KeysBackupState.BackingUp -> BannerState.BackingUp + else -> BannerState.Hidden } - ) - .throttleLast(1000, TimeUnit.MILLISECONDS) // we don't want to flicker or catch transient states + } + + // So recovery is not setup + // Check if cross signing is enabled and local secrets known + if ( + crossSigningInfo.getOrNull() == null + || (crossSigningInfo.getOrNull()?.isTrusted() == true + && pInfo.getOrNull()?.allKnown().orFalse()) + ) { + // So 4S is not setup and we have local secrets, + return@combine BannerState.Setup(numberOfKeys = getNumberOfKeysToBackup()) + } + BannerState.Hidden + + } + .sample(1000) // we don't want to flicker or catch transient states .distinctUntilChanged() .execute { async -> copy( @@ -135,7 +126,7 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS ) } - keyBackupPublishSubject.onNext(session.cryptoService().keysBackupService().state) + keyBackupFlow.tryEmit(session.cryptoService().keysBackupService().state) } /** @@ -165,7 +156,7 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS } override fun onStateChange(newState: KeysBackupState) { - keyBackupPublishSubject.onNext(session.cryptoService().keysBackupService().state) + keyBackupFlow.tryEmit(session.cryptoService().keysBackupService().state) keysBackupState.value = newState } diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt index 8190a37056..057d9e31f8 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.workers.signout import android.net.Uri -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext @@ -35,6 +34,8 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.crypto.keys.KeysExporter +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME @@ -42,7 +43,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_S import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener -import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.flow.flow import timber.log.Timber data class SignoutCheckViewState( @@ -97,7 +98,7 @@ class SignoutCheckViewModel @AssistedInject constructor( ) } - session.rx().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) + session.flow().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) .map { session.sharedSecretStorageService.isRecoverySetup() }