mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Mavericks 2: migrate UserListViewModel
This commit is contained in:
parent
362ebcbe42
commit
acf3b84781
1 changed files with 92 additions and 112 deletions
|
@ -16,12 +16,12 @@
|
|||
|
||||
package im.vector.app.features.userdirectory
|
||||
|
||||
import androidx.lifecycle.asFlow
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
@ -29,21 +29,23 @@ import im.vector.app.core.extensions.exhaustive
|
|||
import im.vector.app.core.extensions.isEmail
|
||||
import im.vector.app.core.extensions.toggle
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import org.matrix.android.sdk.api.MatrixPatterns
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.identity.IdentityServiceListener
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.api.session.profile.ProfileService
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.rx.rx
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private typealias KnownUsersSearch = String
|
||||
private typealias DirectoryUsersSearch = String
|
||||
|
||||
data class ThreePidUser(
|
||||
val email: String,
|
||||
|
@ -54,9 +56,9 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User
|
|||
private val session: Session)
|
||||
: VectorViewModel<UserListViewState, UserListAction, UserListViewEvents>(initialState) {
|
||||
|
||||
private val knownUsersSearch = BehaviorRelay.create<KnownUsersSearch>()
|
||||
private val directoryUsersSearch = BehaviorRelay.create<DirectoryUsersSearch>()
|
||||
private val identityServerUsersSearch = BehaviorRelay.create<String>()
|
||||
private val knownUsersSearch = MutableStateFlow("")
|
||||
private val directoryUsersSearch = MutableStateFlow("")
|
||||
private val identityServerUsersSearch = MutableStateFlow("")
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
@ -77,11 +79,10 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User
|
|||
private val identityServerListener = object : IdentityServiceListener {
|
||||
override fun onIdentityServerChange() {
|
||||
withState {
|
||||
identityServerUsersSearch.accept(it.searchTerm)
|
||||
identityServerUsersSearch.tryEmit(it.searchTerm)
|
||||
val identityServerURL = cleanISURL(session.identityService().getCurrentIdentityServerUrl())
|
||||
setState {
|
||||
copy(
|
||||
configuredIdentityServer = cleanISURL(session.identityService().getCurrentIdentityServerUrl())
|
||||
)
|
||||
copy(configuredIdentityServer = identityServerURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +121,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User
|
|||
private fun handleISUpdateConsent(action: UserListAction.UpdateUserConsent) {
|
||||
session.identityService().setUserConsent(action.consent)
|
||||
withState {
|
||||
identityServerUsersSearch.accept(it.searchTerm)
|
||||
identityServerUsersSearch.tryEmit(it.searchTerm)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,9 +140,9 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User
|
|||
)
|
||||
}
|
||||
}
|
||||
identityServerUsersSearch.accept(searchTerm)
|
||||
knownUsersSearch.accept(searchTerm)
|
||||
directoryUsersSearch.accept(searchTerm)
|
||||
identityServerUsersSearch.tryEmit(searchTerm)
|
||||
knownUsersSearch.tryEmit(searchTerm)
|
||||
directoryUsersSearch.tryEmit(searchTerm)
|
||||
}
|
||||
|
||||
private fun handleShareMyMatrixToLink() {
|
||||
|
@ -151,9 +152,9 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User
|
|||
}
|
||||
|
||||
private fun handleClearSearchUsers() {
|
||||
knownUsersSearch.accept("")
|
||||
directoryUsersSearch.accept("")
|
||||
identityServerUsersSearch.accept("")
|
||||
knownUsersSearch.tryEmit("")
|
||||
directoryUsersSearch.tryEmit("")
|
||||
identityServerUsersSearch.tryEmit("")
|
||||
setState {
|
||||
copy(searchTerm = "")
|
||||
}
|
||||
|
@ -162,103 +163,82 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User
|
|||
private fun observeUsers() = withState { state ->
|
||||
identityServerUsersSearch
|
||||
.filter { it.isEmail() }
|
||||
.throttleLast(300, TimeUnit.MILLISECONDS)
|
||||
.switchMapSingle { search ->
|
||||
val flowSession = session.rx()
|
||||
val stream =
|
||||
flowSession.lookupThreePid(ThreePid.Email(search)).flatMap {
|
||||
it.getOrNull()?.let { foundThreePid ->
|
||||
flowSession.getProfileInfo(foundThreePid.matrixId)
|
||||
.map { json ->
|
||||
ThreePidUser(
|
||||
email = search,
|
||||
user = User(
|
||||
userId = foundThreePid.matrixId,
|
||||
displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String,
|
||||
avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String
|
||||
)
|
||||
)
|
||||
}
|
||||
.onErrorResumeNext {
|
||||
Single.just(ThreePidUser(email = search, user = User(foundThreePid.matrixId)))
|
||||
}
|
||||
} ?: Single.just(ThreePidUser(email = search, user = null))
|
||||
}
|
||||
stream.toAsync {
|
||||
copy(matchingEmail = it)
|
||||
}
|
||||
}
|
||||
.subscribe()
|
||||
.disposeOnClear()
|
||||
.sample(300)
|
||||
.onEach { search ->
|
||||
executeSearchEmail(search)
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
knownUsersSearch
|
||||
.throttleLast(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.switchMap {
|
||||
session.rx().livePagedUsers(it, state.excludedUserIds)
|
||||
}
|
||||
.execute { async ->
|
||||
copy(knownUsers = async)
|
||||
.sample(300)
|
||||
.flowOn(Dispatchers.Main)
|
||||
.flatMapLatest { search ->
|
||||
session.getPagedUsersLive(search, state.excludedUserIds).asFlow()
|
||||
}.execute {
|
||||
copy(knownUsers = it)
|
||||
}
|
||||
|
||||
directoryUsersSearch
|
||||
.debounce(300, TimeUnit.MILLISECONDS)
|
||||
.switchMapSingle { search ->
|
||||
val stream = if (search.isBlank()) {
|
||||
Single.just(emptyList<User>())
|
||||
} else {
|
||||
val searchObservable = session.rx()
|
||||
.searchUsersDirectory(search, 50, state.excludedUserIds.orEmpty())
|
||||
.map { users ->
|
||||
users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() }
|
||||
}
|
||||
// If it's a valid user id try to use Profile API
|
||||
// because directory only returns users that are in public rooms or share a room with you, where as
|
||||
// profile will work other federations
|
||||
if (!MatrixPatterns.isUserId(search)) {
|
||||
searchObservable
|
||||
} else {
|
||||
val profileObservable = session.rx().getProfileInfo(search)
|
||||
.map { json ->
|
||||
User(
|
||||
userId = search,
|
||||
displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String,
|
||||
avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String
|
||||
).toOptional()
|
||||
}
|
||||
.onErrorResumeNext {
|
||||
// Profile API can be restricted and doesn't have to return result.
|
||||
// In this case allow inviting valid user ids.
|
||||
Single.just(
|
||||
User(
|
||||
userId = search,
|
||||
displayName = null,
|
||||
avatarUrl = null
|
||||
).toOptional()
|
||||
)
|
||||
}
|
||||
.debounce(300)
|
||||
.onEach { search ->
|
||||
executeSearchDirectory(state, search)
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
Single.zip(
|
||||
searchObservable,
|
||||
profileObservable,
|
||||
{ searchResults, optionalProfile ->
|
||||
val profile = optionalProfile.getOrNull() ?: return@zip searchResults
|
||||
val searchContainsProfile = searchResults.any { it.userId == profile.userId }
|
||||
if (searchContainsProfile) {
|
||||
searchResults
|
||||
} else {
|
||||
listOf(profile) + searchResults
|
||||
}
|
||||
}
|
||||
private suspend fun executeSearchEmail(search: String) {
|
||||
suspend {
|
||||
val params = listOf(ThreePid.Email(search))
|
||||
val foundThreePid = tryOrNull {
|
||||
session.identityService().lookUp(params).firstOrNull()
|
||||
}
|
||||
if (foundThreePid == null) {
|
||||
null
|
||||
} else {
|
||||
try {
|
||||
val json = session.getProfile(foundThreePid.matrixId)
|
||||
ThreePidUser(
|
||||
email = search,
|
||||
user = User(
|
||||
userId = foundThreePid.matrixId,
|
||||
displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String,
|
||||
avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String
|
||||
)
|
||||
}
|
||||
}
|
||||
stream.toAsync {
|
||||
copy(directoryUsers = it)
|
||||
}
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
ThreePidUser(email = search, user = User(foundThreePid.matrixId))
|
||||
}
|
||||
.subscribe()
|
||||
.disposeOnClear()
|
||||
}
|
||||
}.execute {
|
||||
copy(matchingEmail = it)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun executeSearchDirectory(state: UserListViewState, search: String) {
|
||||
suspend {
|
||||
if (search.isBlank()) {
|
||||
emptyList()
|
||||
} else {
|
||||
val searchResult = session
|
||||
.searchUsersDirectory(search, 50, state.excludedUserIds.orEmpty())
|
||||
.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() }
|
||||
val userProfile = if (MatrixPatterns.isUserId(search)) {
|
||||
val json = tryOrNull { session.getProfile(search) }
|
||||
User(
|
||||
userId = search,
|
||||
displayName = json?.get(ProfileService.DISPLAY_NAME_KEY) as? String,
|
||||
avatarUrl = json?.get(ProfileService.AVATAR_URL_KEY) as? String
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (userProfile == null || searchResult.any { it.userId == userProfile.userId }) {
|
||||
searchResult
|
||||
} else {
|
||||
listOf(userProfile) + searchResult
|
||||
}
|
||||
}
|
||||
}.execute {
|
||||
copy(directoryUsers = it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSelectUser(action: UserListAction.AddPendingSelection) = withState { state ->
|
||||
|
|
Loading…
Reference in a new issue