Mavericks 2: continue replacing Rx

This commit is contained in:
ganfra 2021-10-04 17:50:45 +02:00
parent f72a34ed08
commit fadbb60f90
24 changed files with 417 additions and 355 deletions

View file

@ -15,7 +15,6 @@
*/
package im.vector.app.features.discovery
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
@ -25,17 +24,19 @@ 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.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
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.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.identity.IdentityServiceListener
import org.matrix.android.sdk.api.session.identity.SharedState
import org.matrix.android.sdk.api.session.identity.ThreePid
import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.flow.flow
class DiscoverySettingsViewModel @AssistedInject constructor(
@Assisted initialState: DiscoverySettingsState,
@ -84,12 +85,12 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
}
private fun observeThreePids() {
session.rx()
session.flow()
.liveThreePIds(true)
.subscribe {
.onEach {
retrieveBinding(it)
}
.disposeOnClear()
.launchIn(viewModelScope)
}
override fun onCleared() {

View file

@ -27,6 +27,7 @@ import android.view.MenuItem
import androidx.core.view.GravityCompat
import androidx.core.view.isVisible
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.viewModel
import com.google.android.material.appbar.MaterialToolbar
@ -72,6 +73,7 @@ import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
import im.vector.app.features.workers.signout.ServerBackupStatusViewState
import im.vector.app.push.fcm.FcmHelper
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
@ -288,26 +290,23 @@ class HomeActivity :
}
else -> deepLink
}
permalinkHandler.launch(
context = this,
deepLink = resolvedLink,
navigationInterceptor = this,
buildTask = true
)
// .delay(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { isHandled ->
if (!isHandled) {
val isMatrixToLink = deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE)
|| deepLink.startsWith(MATRIX_TO_CUSTOM_SCHEME_URL_BASE)
MaterialAlertDialogBuilder(this)
.setTitle(R.string.dialog_title_error)
.setMessage(if (isMatrixToLink) R.string.permalink_malformed else R.string.universal_link_malformed)
.setPositiveButton(R.string.ok, null)
.show()
}
}
.disposeOnDestroy()
lifecycleScope.launch {
val isHandled = permalinkHandler.launch(
context = this@HomeActivity,
deepLink = resolvedLink,
navigationInterceptor = this@HomeActivity,
buildTask = true
)
if (!isHandled) {
val isMatrixToLink = deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE)
|| deepLink.startsWith(MATRIX_TO_CUSTOM_SCHEME_URL_BASE)
MaterialAlertDialogBuilder(this@HomeActivity)
.setTitle(R.string.dialog_title_error)
.setMessage(if (isMatrixToLink) R.string.permalink_malformed else R.string.universal_link_malformed)
.setPositiveButton(R.string.ok, null)
.show()
}
}
}
}

View file

@ -16,6 +16,7 @@
package im.vector.app.features.home
import androidx.lifecycle.asFlow
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.MavericksViewModelFactory
@ -31,6 +32,8 @@ import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
@ -44,6 +47,7 @@ import org.matrix.android.sdk.api.session.initsync.SyncStatusService
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.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.util.awaitCallback
@ -100,9 +104,9 @@ class HomeActivityViewModel @AssistedInject constructor(
.crossSigningService().allPrivateKeysKnown()
safeActiveSession
.rx()
.flow()
.liveCrossSigningInfo(safeActiveSession.myUserId)
.subscribe {
.onEach {
val isVerified = it.getOrNull()?.isTrusted() ?: false
if (!isVerified && onceTrusted) {
// cross signing keys have been reset
@ -116,15 +120,15 @@ class HomeActivityViewModel @AssistedInject constructor(
}
onceTrusted = isVerified
}
.disposeOnClear()
.launchIn(viewModelScope)
}
private fun observeInitialSync() {
val session = activeSessionHolder.getSafeActiveSession() ?: return
session.getSyncStatusLive()
.asObservable()
.subscribe { status ->
.asFlow()
.onEach { status ->
when (status) {
is SyncStatusService.Status.Progressing -> {
// Schedule a check of the bootstrap when the init sync will be finished
@ -145,7 +149,7 @@ class HomeActivityViewModel @AssistedInject constructor(
)
}
}
.disposeOnClear()
.launchIn(viewModelScope)
}
/**

View file

@ -16,6 +16,7 @@
package im.vector.app.features.home
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.ViewModelContext
@ -37,6 +38,7 @@ 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.filterIsInstance
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter
@ -48,7 +50,6 @@ 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
import java.util.concurrent.TimeUnit
@ -182,25 +183,18 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
}
private fun observeSyncState() {
session.rx()
session.flow()
.liveSyncState()
.subscribe { syncState ->
setState {
copy(syncState = syncState)
}
.setOnEach { syncState ->
copy(syncState = syncState)
}
.disposeOnClear()
session.getSyncStatusLive()
.asObservable()
.subscribe {
if (it is SyncStatusService.Status.IncrementalSyncStatus) {
setState {
copy(incrementalSyncStatus = it)
}
}
.asFlow()
.filterIsInstance<SyncStatusService.Status.IncrementalSyncStatus>()
.setOnEach {
copy(incrementalSyncStatus = it)
}
.disposeOnClear()
}
private fun observeRoomGroupingMethod() {

View file

@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue
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.flow.flow
import org.matrix.android.sdk.rx.rx
class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: BreadcrumbsViewState,
@ -61,12 +62,11 @@ class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: B
// PRIVATE METHODS *****************************************************************************
private fun observeBreadcrumbs() {
session.rx()
session.flow()
.liveBreadcrumbs(roomSummaryQueryParams {
displayName = QueryStringValue.NoCondition
memberships = listOf(Membership.JOIN)
})
.observeOn(Schedulers.computation())
.execute { asyncBreadcrumbs ->
copy(asyncBreadcrumbs = asyncBreadcrumbs)
}

View file

@ -1590,57 +1590,54 @@ class RoomDetailFragment @Inject constructor(
}
}
// TimelineEventController.Callback ************************************************************
// TimelineEventController.Callback ************************************************************
override fun onUrlClicked(url: String, title: String): Boolean {
permalinkHandler
.launch(requireActivity(), url, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean {
// Same room?
if (roomId == roomDetailArgs.roomId) {
// Navigation to same room
if (eventId == null) {
showSnackWithMessage(getString(R.string.navigate_to_room_when_already_in_the_room))
} else {
// Highlight and scroll to this event
roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(eventId, true))
viewLifecycleOwner.lifecycleScope.launch {
val isManaged = permalinkHandler
.launch(requireActivity(), url, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean {
// Same room?
if (roomId == roomDetailArgs.roomId) {
// Navigation to same room
if (eventId == null) {
showSnackWithMessage(getString(R.string.navigate_to_room_when_already_in_the_room))
} else {
// Highlight and scroll to this event
roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(eventId, true))
}
return true
}
// Not handled
return false
}
override fun navToMemberProfile(userId: String, deepLink: Uri): Boolean {
openRoomMemberProfile(userId)
return true
}
// Not handled
return false
}
override fun navToMemberProfile(userId: String, deepLink: Uri): Boolean {
openRoomMemberProfile(userId)
return true
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { managed ->
if (!managed) {
if (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) {
MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_NegativeDestructive)
.setTitle(R.string.external_link_confirmation_title)
.setMessage(
getString(R.string.external_link_confirmation_message, title, url)
.toSpannable()
.colorizeMatchingText(url, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary))
.colorizeMatchingText(title, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary))
)
.setPositiveButton(R.string._continue) { _, _ ->
openUrlInExternalBrowser(requireContext(), url)
}
.setNegativeButton(R.string.cancel, null)
.show()
} else {
// Open in external browser, in a new Tab
openUrlInExternalBrowser(requireContext(), url)
}
}
})
if (!isManaged) {
if (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) {
MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_NegativeDestructive)
.setTitle(R.string.external_link_confirmation_title)
.setMessage(
getString(R.string.external_link_confirmation_message, title, url)
.toSpannable()
.colorizeMatchingText(url, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary))
.colorizeMatchingText(title, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary))
)
.setPositiveButton(R.string._continue) { _, _ ->
openUrlInExternalBrowser(requireContext(), url)
}
.setNegativeButton(R.string.cancel, null)
.show()
} else {
// Open in external browser, in a new Tab
openUrlInExternalBrowser(requireContext(), url)
}
.disposeOnDestroyView()
}
}
// In fact it is always managed
return true
}
@ -1799,15 +1796,15 @@ class RoomDetailFragment @Inject constructor(
}
override fun onRoomCreateLinkClicked(url: String) {
permalinkHandler
.launch(requireContext(), url, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean {
requireActivity().finish()
return false
}
})
.subscribe()
.disposeOnDestroyView()
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
permalinkHandler
.launch(requireContext(), url, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean {
requireActivity().finish()
return false
}
})
}
}
override fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>) {

View file

@ -18,10 +18,14 @@ package im.vector.app.features.invite
import im.vector.app.ActiveSessionDataSource
import im.vector.app.features.session.coroutineScope
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import org.matrix.android.sdk.api.extensions.orFalse
@ -31,9 +35,8 @@ import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
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 timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
@ -50,7 +53,7 @@ class InvitesAcceptor @Inject constructor(
private lateinit var activeSessionDisposable: Disposable
private val shouldRejectRoomIds = mutableSetOf<String>()
private val invitedRoomDisposables = HashMap<String, Disposable>()
private val activeSessionIds = mutableSetOf<String>()
private val semaphore = Semaphore(1)
fun initialize() {
@ -71,34 +74,32 @@ class InvitesAcceptor @Inject constructor(
if (!autoAcceptInvites.isEnabled) {
return
}
if (invitedRoomDisposables.containsKey(session.sessionId)) {
if (activeSessionIds.contains(session.sessionId)) {
return
}
activeSessionIds.add(session.sessionId)
session.addListener(this)
val roomQueryParams = roomSummaryQueryParams {
this.memberships = listOf(Membership.INVITE)
}
val rxSession = session.rx()
Observable
.combineLatest(
rxSession.liveRoomSummaries(roomQueryParams),
rxSession.liveRoomChangeMembershipState().debounce(1, TimeUnit.SECONDS),
{ invitedRooms, _ -> invitedRooms.map { it.roomId } }
)
val flowSession = session.flow()
combine(
flowSession.liveRoomSummaries(roomQueryParams),
flowSession.liveRoomChangeMembershipState().debounce(1000)
) { invitedRooms, _ -> invitedRooms.map { it.roomId } }
.filter { it.isNotEmpty() }
.subscribe { invitedRoomIds ->
session.coroutineScope.launch {
semaphore.withPermit {
Timber.v("Invited roomIds: $invitedRoomIds")
for (roomId in invitedRoomIds) {
async { session.joinRoomSafely(roomId) }.start()
}
}
}
}
.also {
invitedRoomDisposables[session.sessionId] = it
}
.onEach { invitedRoomIds ->
joinInvitedRooms(session, invitedRoomIds)
}.launchIn(session.coroutineScope)
}
private suspend fun joinInvitedRooms(session: Session, invitedRoomIds: List<String>) = coroutineScope {
semaphore.withPermit {
Timber.v("Invited roomIds: $invitedRoomIds")
for (roomId in invitedRoomIds) {
async { session.joinRoomSafely(roomId) }.start()
}
}
}
private suspend fun Session.joinRoomSafely(roomId: String) {
@ -138,6 +139,6 @@ class InvitesAcceptor @Inject constructor(
override fun onSessionStopped(session: Session) {
session.removeListener(this)
invitedRoomDisposables.remove(session.sessionId)?.dispose()
activeSessionIds.remove(session.sessionId)
}
}

View file

@ -23,10 +23,10 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.utils.toast
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
@ -34,80 +34,71 @@ import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.rx.rx
import javax.inject.Inject
class PermalinkHandler @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
private val navigator: Navigator) {
fun launch(
suspend fun launch(
context: Context,
deepLink: String?,
navigationInterceptor: NavigationInterceptor? = null,
buildTask: Boolean = false
): Single<Boolean> {
): Boolean {
val uri = deepLink?.let { Uri.parse(it) }
return launch(context, uri, navigationInterceptor, buildTask)
}
fun launch(
suspend fun launch(
context: Context,
deepLink: Uri?,
navigationInterceptor: NavigationInterceptor? = null,
buildTask: Boolean = false
): Single<Boolean> {
): Boolean {
if (deepLink == null || !isPermalinkSupported(context, deepLink.toString())) {
return Single.just(false)
return false
}
return Single
.fromCallable {
PermalinkParser.parse(deepLink)
}
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.flatMap { permalinkData ->
handlePermalink(permalinkData, deepLink, context, navigationInterceptor, buildTask)
}
.onErrorReturnItem(false)
return tryOrNull {
withContext(Dispatchers.Default) {
val permalinkData = PermalinkParser.parse(deepLink)
handlePermalink(permalinkData, deepLink, context, navigationInterceptor, buildTask)
}
} ?: false
}
private fun handlePermalink(
private suspend fun handlePermalink(
permalinkData: PermalinkData,
rawLink: Uri,
context: Context,
navigationInterceptor: NavigationInterceptor?,
buildTask: Boolean
): Single<Boolean> {
): Boolean {
return when (permalinkData) {
is PermalinkData.RoomLink -> {
permalinkData.getRoomId()
.observeOn(AndroidSchedulers.mainThread())
.map {
val roomId = it.getOrNull()
if (navigationInterceptor?.navToRoom(roomId, permalinkData.eventId, rawLink) != true) {
openRoom(
context = context,
roomId = roomId,
permalinkData = permalinkData,
rawLink = rawLink,
buildTask = buildTask
)
}
true
}
val roomId = permalinkData.getRoomId()
if (navigationInterceptor?.navToRoom(roomId, permalinkData.eventId, rawLink) != true) {
openRoom(
context = context,
roomId = roomId,
permalinkData = permalinkData,
rawLink = rawLink,
buildTask = buildTask
)
}
true
}
is PermalinkData.GroupLink -> {
navigator.openGroupDetail(permalinkData.groupId, context, buildTask)
Single.just(true)
true
}
is PermalinkData.UserLink -> {
if (navigationInterceptor?.navToMemberProfile(permalinkData.userId, rawLink) != true) {
navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask)
}
Single.just(true)
true
}
is PermalinkData.FallbackLink -> {
Single.just(false)
false
}
is PermalinkData.RoomEmailInviteLink -> {
val data = RoomPreviewData(
@ -118,7 +109,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
roomType = permalinkData.roomType
)
navigator.openRoomPreview(context, data)
Single.just(true)
true
}
}
}
@ -130,15 +121,13 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
}
}
private fun PermalinkData.RoomLink.getRoomId(): Single<Optional<String>> {
private suspend fun PermalinkData.RoomLink.getRoomId(): String? {
val session = activeSessionHolder.getSafeActiveSession()
return if (isRoomAlias && session != null) {
session.rx()
.getRoomIdByAlias(roomIdOrAlias, true)
.map { it.getOrNull()?.roomId.toOptional() }
.subscribeOn(Schedulers.io())
val roomIdByAlias = session.getRoomIdByAlias(roomIdOrAlias, true)
roomIdByAlias.getOrNull()?.roomId
} else {
Single.just(Optional.from(roomIdOrAlias))
roomIdOrAlias
}
}

View file

@ -20,16 +20,26 @@ import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay
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.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.subscribe
import kotlinx.coroutines.flow.switchMap
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.EventType
@ -37,8 +47,8 @@ import org.matrix.android.sdk.api.session.room.Room
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.util.Optional
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
/**
* This ViewModel observe a room summary and notify when the room is left
@ -66,28 +76,31 @@ class RequireActiveMembershipViewModel @AssistedInject constructor(
}
}
private val roomIdObservable = BehaviorRelay.createDefault(Optional.from(initialState.roomId))
private val roomIdFlow = MutableStateFlow(Optional.from(initialState.roomId))
init {
observeRoomSummary()
}
private fun observeRoomSummary() {
roomIdObservable
roomIdFlow
.unwrap()
.switchMap { roomId ->
val room = session.getRoom(roomId) ?: return@switchMap Observable.just(Optional.empty<RequireActiveMembershipViewEvents.RoomLeft>())
room.rx()
.flatMapLatest { roomId ->
val room = session.getRoom(roomId) ?: return@flatMapLatest flow{
val emptyResult = Optional.empty<RequireActiveMembershipViewEvents.RoomLeft>()
emit(emptyResult)
}
room.flow()
.liveRoomSummary()
.unwrap()
.observeOn(Schedulers.computation())
.flowOn(Dispatchers.Default)
.map { mapToLeftViewEvent(room, it) }
}
.unwrap()
.subscribe { event ->
.onEach { event ->
_viewEvents.post(event)
}
.disposeOnClear()
.launchIn(viewModelScope)
}
private fun mapToLeftViewEvent(room: Room, roomSummary: RoomSummary): Optional<RequireActiveMembershipViewEvents.RoomLeft> {
@ -128,7 +141,7 @@ class RequireActiveMembershipViewModel @AssistedInject constructor(
setState {
copy(roomId = action.roomId)
}
roomIdObservable.accept(Optional.from(action.roomId))
roomIdFlow.tryEmit(Optional.from(action.roomId))
}
}.exhaustive
}

View file

@ -22,6 +22,7 @@ import android.view.LayoutInflater
import android.view.MenuItem
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
@ -37,6 +38,7 @@ 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.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
@ -125,20 +127,20 @@ class PublicRoomsFragment @Inject constructor(
}
override fun onUnknownRoomClicked(roomIdOrAlias: String) {
val permalink = session.permalinkService().createPermalink(roomIdOrAlias)
permalinkHandler
.launch(requireContext(), permalink, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean {
requireActivity().finish()
return false
}
})
.subscribe { isSuccessful ->
if (!isSuccessful) {
requireContext().toast(R.string.room_error_not_found)
}
}
.disposeOnDestroyView()
viewLifecycleOwner.lifecycleScope.launch {
val permalink = session.permalinkService().createPermalink(roomIdOrAlias)
val isHandled = permalinkHandler
.launch(requireContext(), permalink, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean {
requireActivity().finish()
return false
}
})
if (!isHandled) {
requireContext().toast(R.string.room_error_not_found)
}
}
}
override fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: JoinState) {

View file

@ -16,7 +16,6 @@
package im.vector.app.features.roomdirectory
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
@ -31,6 +30,7 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
@ -38,7 +38,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.flow.flow
import timber.log.Timber
class RoomDirectoryViewModel @AssistedInject constructor(
@ -80,28 +80,24 @@ class RoomDirectoryViewModel @AssistedInject constructor(
memberships = listOf(Membership.JOIN)
}
session
.rx()
.flow()
.liveRoomSummaries(queryParams)
.subscribe { list ->
val joinedRoomIds = list
?.map { it.roomId }
?.toSet()
.orEmpty()
setState {
copy(joinedRoomsIds = joinedRoomIds)
}
.map { roomSummaries ->
roomSummaries
.map { it.roomId }
.toSet()
}
.setOnEach {
copy(joinedRoomsIds = it)
}
.disposeOnClear()
}
private fun observeMembershipChanges() {
session.rx()
session.flow()
.liveRoomChangeMembershipState()
.subscribe {
setState { copy(changeMembershipStates = it) }
.setOnEach {
copy(changeMembershipStates = it)
}
.disposeOnClear()
}
override fun handle(action: RoomDirectoryAction) {

View file

@ -30,6 +30,8 @@ import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.roomdirectory.JoinState
import kotlinx.coroutines.Dispatchers
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.query.QueryStringValue
@ -40,6 +42,7 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
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
@ -165,9 +168,9 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
excludeType = null
}
session
.rx()
.flow()
.liveRoomSummaries(queryParams)
.subscribe { list ->
.onEach { list ->
val isRoomJoined = list.any {
it.membership == Membership.JOIN
}
@ -180,13 +183,13 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
setState { copy(roomJoinState = JoinState.JOINED) }
}
}
.disposeOnClear()
.launchIn(viewModelScope)
}
private fun observeMembershipChanges() {
session.rx()
session.flow()
.liveRoomChangeMembershipState()
.subscribe {
.onEach {
val changeMembership = it[initialState.roomId] ?: ChangeMembershipState.Unknown
val joinState = when (changeMembership) {
is ChangeMembershipState.Joining -> JoinState.JOINING
@ -198,7 +201,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
setState { copy(roomJoinState = joinState) }
}
}
.disposeOnClear()
.launchIn(viewModelScope)
}
override fun handle(action: RoomPreviewAction) {

View file

@ -40,10 +40,10 @@ import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.api.session.room.state.isPublic
import org.matrix.android.sdk.rx.RxRoom
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.FlowRoom
import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.flow.mapOptional
import org.matrix.android.sdk.flow.unwrap
class RoomProfileViewModel @AssistedInject constructor(
@Assisted private val initialState: RoomProfileViewState,
@ -69,15 +69,15 @@ class RoomProfileViewModel @AssistedInject constructor(
private val room = session.getRoom(initialState.roomId)!!
init {
val rxRoom = room.rx()
observeRoomSummary(rxRoom)
observeRoomCreateContent(rxRoom)
observeBannedRoomMembers(rxRoom)
val flowRoom = room.flow()
observeRoomSummary(flowRoom)
observeRoomCreateContent(flowRoom)
observeBannedRoomMembers(flowRoom)
observePermissions()
}
private fun observeRoomCreateContent(rxRoom: RxRoom) {
rxRoom.liveStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.NoCondition)
private fun observeRoomCreateContent(flowRoom: FlowRoom) {
flowRoom.liveStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.NoCondition)
.mapOptional { it.content.toModel<RoomCreateContent>() }
.unwrap()
.execute { async ->
@ -92,16 +92,16 @@ class RoomProfileViewModel @AssistedInject constructor(
}
}
private fun observeRoomSummary(rxRoom: RxRoom) {
rxRoom.liveRoomSummary()
private fun observeRoomSummary(flowRoom: FlowRoom) {
flowRoom.liveRoomSummary()
.unwrap()
.execute {
copy(roomSummary = it)
}
}
private fun observeBannedRoomMembers(rxRoom: RxRoom) {
rxRoom.liveRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.BAN) })
private fun observeBannedRoomMembers(flowRoom: FlowRoom) {
flowRoom.liveRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.BAN) })
.execute {
copy(bannedMembership = it)
}

View file

@ -28,11 +28,10 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
import im.vector.app.features.settings.VectorPreferences
import io.reactivex.Completable
import io.reactivex.Observable
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
@ -47,7 +46,6 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
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
class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState,
private val vectorPreferences: VectorPreferences,
@ -259,61 +257,57 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
}
private fun saveSettings() = withState { state ->
postLoading(true)
val operationList = mutableListOf<Completable>()
val operationList = mutableListOf<suspend () -> Unit>()
val summary = state.roomSummary.invoke()
when (val avatarAction = state.avatarAction) {
RoomSettingsViewState.AvatarAction.None -> Unit
RoomSettingsViewState.AvatarAction.DeleteAvatar -> {
operationList.add(room.rx().deleteAvatar())
operationList.add { room.deleteAvatar() }
}
is RoomSettingsViewState.AvatarAction.UpdateAvatar -> {
operationList.add(room.rx().updateAvatar(avatarAction.newAvatarUri, avatarAction.newAvatarFileName))
operationList.add { room.updateAvatar(avatarAction.newAvatarUri, avatarAction.newAvatarFileName) }
}
}
if (summary?.name != state.newName) {
operationList.add(room.rx().updateName(state.newName ?: ""))
operationList.add { room.updateName(state.newName ?: "") }
}
if (summary?.topic != state.newTopic) {
operationList.add(room.rx().updateTopic(state.newTopic ?: ""))
operationList.add { room.updateTopic(state.newTopic ?: "") }
}
if (state.newHistoryVisibility != null) {
operationList.add(room.rx().updateHistoryReadability(state.newHistoryVisibility))
operationList.add { room.updateHistoryReadability(state.newHistoryVisibility) }
}
if (state.newRoomJoinRules.hasChanged()) {
operationList.add(room.rx().updateJoinRule(state.newRoomJoinRules.newJoinRules, state.newRoomJoinRules.newGuestAccess))
operationList.add { room.updateJoinRule(state.newRoomJoinRules.newJoinRules, state.newRoomJoinRules.newGuestAccess) }
}
viewModelScope.launch {
updateLoadingState(isLoading = true)
try {
for (operation in operationList) {
operation.invoke()
}
setState {
deletePendingAvatar(this)
copy(
avatarAction = RoomSettingsViewState.AvatarAction.None,
newHistoryVisibility = null,
newRoomJoinRules = RoomSettingsViewState.NewJoinRule()
)
}
_viewEvents.post(RoomSettingsViewEvents.Success)
} catch (failure: Throwable) {
_viewEvents.post(RoomSettingsViewEvents.Failure(failure))
}finally {
updateLoadingState(isLoading = false)
}
}
Observable
.fromIterable(operationList)
.concatMapCompletable { it }
.subscribe(
{
postLoading(false)
setState {
deletePendingAvatar(this)
copy(
avatarAction = RoomSettingsViewState.AvatarAction.None,
newHistoryVisibility = null,
newRoomJoinRules = RoomSettingsViewState.NewJoinRule()
)
}
_viewEvents.post(RoomSettingsViewEvents.Success)
},
{
postLoading(false)
_viewEvents.post(RoomSettingsViewEvents.Failure(it))
}
)
.disposeOnClear()
}
private fun postLoading(isLoading: Boolean) {
private fun updateLoadingState(isLoading: Boolean) {
setState {
copy(isLoading = isLoading)
}

View file

@ -0,0 +1,71 @@
/*
* 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.features.settings
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.rx.SecretsSynchronisationInfo
data class SecretsSynchronisationInfo(
val isBackupSetup: Boolean,
val isCrossSigningEnabled: Boolean,
val isCrossSigningTrusted: Boolean,
val allPrivateKeysKnown: Boolean,
val megolmBackupAvailable: Boolean,
val megolmSecretKnown: Boolean,
val isMegolmKeyIn4S: Boolean
)
fun Session.liveSecretSynchronisationInfo(): Flow<SecretsSynchronisationInfo> {
val sessionFlow = flow()
return combine(
sessionFlow.liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)),
sessionFlow.liveCrossSigningInfo(myUserId),
sessionFlow.liveCrossSigningPrivateKeys()
) { _, crossSigningInfo, pInfo ->
// first check if 4S is already setup
val is4SSetup = sharedSecretStorageService.isRecoverySetup()
val isCrossSigningEnabled = crossSigningInfo.getOrNull() != null
val isCrossSigningTrusted = crossSigningInfo.getOrNull()?.isTrusted() == true
val allPrivateKeysKnown = pInfo.getOrNull()?.allKnown().orFalse()
val keysBackupService = cryptoService().keysBackupService()
val currentBackupVersion = keysBackupService.currentBackupVersion
val megolmBackupAvailable = currentBackupVersion != null
val savedBackupKey = keysBackupService.getKeyBackupRecoveryKeyInfo()
val megolmKeyKnown = savedBackupKey?.version == currentBackupVersion
SecretsSynchronisationInfo(
isBackupSetup = is4SSetup,
isCrossSigningEnabled = isCrossSigningEnabled,
isCrossSigningTrusted = isCrossSigningTrusted,
allPrivateKeysKnown = allPrivateKeysKnown,
megolmBackupAvailable = megolmBackupAvailable,
megolmSecretKnown = megolmKeyKnown,
isMegolmKeyIn4S = sharedSecretStorageService.isMegolmKeyInBackup()
)
}
.distinctUntilChanged()
}

View file

@ -60,6 +60,10 @@ import im.vector.app.features.raw.wellknown.isE2EByDefault
import im.vector.app.features.themes.ThemeUtils
import io.reactivex.android.schedulers.AndroidSchedulers
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.launch
import me.gujun.android.span.span
import org.matrix.android.sdk.api.MatrixCallback
@ -144,14 +148,12 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
// My device name may have been updated
refreshMyDevice()
refreshXSigningStatus()
session.rx().liveSecretSynchronisationInfo()
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
session.liveSecretSynchronisationInfo()
.flowOn(Dispatchers.Main)
.onEach {
refresh4SSection(it)
refreshXSigningStatus()
}.also {
disposables.add(it)
}
}.launchIn(viewLifecycleOwner.lifecycleScope)
lifecycleScope.launchWhenResumed {
findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible =

View file

@ -16,7 +16,6 @@
package im.vector.app.features.settings.ignored
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
@ -27,14 +26,14 @@ 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.VectorViewModel
import im.vector.app.core.platform.VectorViewModelAction
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.user.model.User
import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.flow.flow
data class IgnoredUsersViewState(
val ignoredUsers: List<User> = emptyList(),
@ -68,7 +67,7 @@ class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState:
}
private fun observeIgnoredUsers() {
session.rx()
session.flow()
.liveIgnoredUsers()
.execute { async ->
copy(

View file

@ -19,24 +19,25 @@ package im.vector.app.features.share
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay
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.extensions.toggle
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.attachments.isPreviewable
import im.vector.app.features.attachments.toGroupedContentAttachmentData
import im.vector.app.features.home.room.list.BreadcrumbsRoomComparator
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.sample
import org.matrix.android.sdk.api.query.QueryStringValue
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
class IncomingShareViewModel @AssistedInject constructor(
@Assisted initialState: IncomingShareViewState,
@ -58,7 +59,7 @@ class IncomingShareViewModel @AssistedInject constructor(
}
}
private val filterStream: BehaviorRelay<String> = BehaviorRelay.createDefault("")
private val filterStream = MutableStateFlow("")
init {
observeRoomSummaries()
@ -75,7 +76,7 @@ class IncomingShareViewModel @AssistedInject constructor(
}
filterStream
.switchMap { filter ->
.flatMapLatest { filter ->
val displayNameQuery = if (filter.isEmpty()) {
QueryStringValue.NoCondition
} else {
@ -85,9 +86,9 @@ class IncomingShareViewModel @AssistedInject constructor(
displayName = displayNameQuery
memberships = listOf(Membership.JOIN)
}
session.rx().liveRoomSummaries(filterQueryParams)
session.flow().liveRoomSummaries(filterQueryParams)
}
.throttleLast(300, TimeUnit.MILLISECONDS)
.sample(300)
.map { it.sortedWith(breadcrumbsRoomComparator) }
.execute {
copy(filteredRoomSummaries = it)
@ -110,7 +111,7 @@ class IncomingShareViewModel @AssistedInject constructor(
}
private fun handleFilter(action: IncomingShareAction.FilterWith) {
filterStream.accept(action.filter)
filterStream.tryEmit(action.filter)
}
private fun handleShareToSelectedRooms() = withState { state ->

View file

@ -16,7 +16,7 @@
package im.vector.app.features.spaces
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModelFactory
@ -33,8 +33,9 @@ import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.group
import im.vector.app.space
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
@ -44,19 +45,16 @@ import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.group.groupSummaryQueryParams
import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
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.api.session.room.summary.RoomAggregateNotificationCount
import org.matrix.android.sdk.api.session.space.SpaceOrderUtils
import org.matrix.android.sdk.api.session.space.model.SpaceOrderContent
import org.matrix.android.sdk.api.session.space.model.TopLevelSpaceComparator
import org.matrix.android.sdk.api.session.user.model.User
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 java.util.concurrent.TimeUnit
class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: SpaceListViewState,
@ -286,21 +284,23 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
null)
}
val rxSession = session.rx()
val flowSession = session.flow()
Observable.combineLatest<User?, List<RoomSummary>, List<RoomAccountDataEvent>, List<RoomSummary>>(
rxSession
combine(
flowSession
.liveUser(session.myUserId)
.map {
it.getOrNull()
},
rxSession
flowSession
.liveSpaceSummaries(spaceSummaryQueryParams),
session.accountDataService().getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)).asObservable(),
{ _, communityGroups, _ ->
communityGroups
}
)
session
.accountDataService()
.getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER))
.asFlow()
) { _, communityGroups, _ ->
communityGroups
}
.execute { async ->
val rootSpaces = session.spaceService().getRootSpaceSummaries()
val orders = rootSpaces.map {

View file

@ -25,6 +25,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.text.toSpannable
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.epoxy.EpoxyVisibilityTracker
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
@ -46,8 +47,7 @@ import im.vector.app.features.permalink.PermalinkHandler
import im.vector.app.features.spaces.manage.ManageType
import im.vector.app.features.spaces.manage.SpaceAddRoomSpaceChooserBottomSheet
import im.vector.app.features.spaces.manage.SpaceManageActivity
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import java.net.URL
@ -200,33 +200,29 @@ class SpaceDirectoryFragment @Inject constructor(
}
override fun onUrlClicked(url: String, title: String): Boolean {
permalinkHandler
.launch(requireActivity(), url, null)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { managed ->
if (!managed) {
if (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) {
MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive)
.setTitle(R.string.external_link_confirmation_title)
.setMessage(
getString(R.string.external_link_confirmation_message, title, url)
.toSpannable()
.colorizeMatchingText(url, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary))
.colorizeMatchingText(title, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary))
)
.setPositiveButton(R.string._continue) { _, _ ->
openUrlInExternalBrowser(requireContext(), url)
}
.setNegativeButton(R.string.cancel, null)
.show()
} else {
// Open in external browser, in a new Tab
openUrlInExternalBrowser(requireContext(), url)
}
}
viewLifecycleOwner.lifecycleScope.launch {
val isHandled = permalinkHandler.launch(requireActivity(), url, null)
if (!isHandled) {
if (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) {
MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive)
.setTitle(R.string.external_link_confirmation_title)
.setMessage(
getString(R.string.external_link_confirmation_message, title, url)
.toSpannable()
.colorizeMatchingText(url, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary))
.colorizeMatchingText(title, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary))
)
.setPositiveButton(R.string._continue) { _, _ ->
openUrlInExternalBrowser(requireContext(), url)
}
.setNegativeButton(R.string.cancel, null)
.show()
} else {
// Open in external browser, in a new Tab
openUrlInExternalBrowser(requireContext(), url)
}
.disposeOnDestroyView()
}
}
// In fact it is always managed
return true
}

View file

@ -31,6 +31,7 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
@ -42,7 +43,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.flow.flow
import timber.log.Timber
class SpaceDirectoryViewModel @AssistedInject constructor(
@ -147,7 +148,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
excludeType = null
}
session
.rx()
.flow()
.liveRoomSummaries(queryParams)
.map {
it.map { it.roomId }.toSet()
@ -158,12 +159,11 @@ class SpaceDirectoryViewModel @AssistedInject constructor(
}
private fun observeMembershipChanges() {
session.rx()
session.flow()
.liveRoomChangeMembershipState()
.subscribe {
setState { copy(changeMembershipStates = it) }
.setOnEach {
copy(changeMembershipStates = it)
}
.disposeOnClear()
}
override fun handle(action: SpaceDirectoryViewAction) {

View file

@ -160,16 +160,15 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User
}
private fun observeUsers() = withState { state ->
identityServerUsersSearch
.filter { it.isEmail() }
.throttleLast(300, TimeUnit.MILLISECONDS)
.switchMapSingle { search ->
val rx = session.rx()
val flowSession = session.rx()
val stream =
rx.lookupThreePid(ThreePid.Email(search)).flatMap {
flowSession.lookupThreePid(ThreePid.Email(search)).flatMap {
it.getOrNull()?.let { foundThreePid ->
rx.getProfileInfo(foundThreePid.matrixId)
flowSession.getProfileInfo(foundThreePid.matrixId)
.map { json ->
ThreePidUser(
email = search,

View file

@ -30,6 +30,7 @@ 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.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.query.QueryStringValue
@ -44,7 +45,6 @@ import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure
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 timber.log.Timber
import javax.net.ssl.HttpsURLConnection
@ -135,7 +135,7 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi
return
}
val widgetId = initialState.widgetId ?: return
session.rx()
session.flow()
.liveRoomWidgets(initialState.roomId, QueryStringValue.Equals(widgetId))
.filter { it.isNotEmpty() }
.map { it.first() }

View file

@ -15,23 +15,24 @@
*/
package im.vector.app.features.widgets.permissions
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MavericksViewModelFactory
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.platform.VectorViewModel
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.flow.flow
import timber.log.Timber
import java.net.URL
@ -48,7 +49,7 @@ class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val in
private fun observeWidget() {
val widgetId = initialState.widgetId ?: return
session.rx()
session.flow()
.liveRoomWidgets(initialState.roomId, QueryStringValue.Equals(widgetId))
.filter { it.isNotEmpty() }
.map {