Direct messages: try to handle selecting/deselecting users (WIP)

This commit is contained in:
ganfra 2019-07-23 19:53:47 +02:00
parent 03974c8bdf
commit 125eacb20b
14 changed files with 184 additions and 84 deletions

View File

@ -28,7 +28,7 @@ object MatrixPatterns {
// regex pattern to find matrix user ids in a string.
// See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids
private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX"
private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)

// regex pattern to find room ids in a string.
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX"

View File

@ -18,6 +18,7 @@ package im.vector.riotx.core.extensions

import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import im.vector.riotx.core.utils.FirstThrottler
import im.vector.riotx.core.utils.EventObserver
@ -44,3 +45,7 @@ inline fun <T> LiveData<LiveEvent<T>>.observeEventFirstThrottle(owner: Lifecycle
}
})
}

fun <T> MutableLiveData<LiveEvent<T>>.postLiveEvent(content: T) {
this.postValue(LiveEvent(content))
}

View File

@ -19,6 +19,7 @@ package im.vector.riotx.core.mvrx
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.utils.LiveEvent

abstract class NavigationViewModel<NavigationClass> : ViewModel() {
@ -29,6 +30,6 @@ abstract class NavigationViewModel<NavigationClass> : ViewModel() {


fun goTo(navigation: NavigationClass) {
_navigateTo.postValue(LiveEvent(navigation))
_navigateTo.postLiveEvent(navigation)
}
}

View File

@ -16,20 +16,36 @@

package im.vector.riotx.core.platform

import com.airbnb.mvrx.BaseMvRxViewModel
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.*
import im.vector.matrix.android.api.util.CancelableBag
import im.vector.riotx.BuildConfig
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.disposables.Disposable

abstract class VectorViewModel<S : MvRxState>(initialState: S)
: BaseMvRxViewModel<S>(initialState, false) {

protected val cancelableBag = CancelableBag()

override fun onCleared() {
super.onCleared()
cancelableBag.cancel()
/**
* This method does the same thing as the execute function, but it doesn't subscribe to the stream
* so you can use this in a switchMap or a flatMap
*/
fun <T> Single<T>.toAsync(stateReducer: S.(Async<T>) -> S): Single<Async<T>> {
setState { stateReducer(Loading()) }
return this.map { Success(it) as Async<T> }
.onErrorReturn { Fail(it) }
.doOnSuccess { setState { stateReducer(it) } }
}

/**
* This method does the same thing as the execute function, but it doesn't subscribe to the stream
* so you can use this in a switchMap or a flatMap
*/
fun <T> Observable<T>.toAsync(stateReducer: S.(Async<T>) -> S): Observable<Async<T>> {
setState { stateReducer(Loading()) }
return this.map { Success(it) as Async<T> }
.onErrorReturn { Fail(it) }
.doOnNext { setState { stateReducer(it) } }
}

}

View File

@ -19,13 +19,9 @@
package im.vector.riotx.features.home.createdirect

import com.airbnb.epoxy.EpoxyController
import com.airbnb.epoxy.VisibilityState
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.internal.util.firstLetterOfDisplayName
import im.vector.riotx.core.epoxy.errorWithRetryItem
@ -60,7 +56,7 @@ class CreateDirectRoomController @Inject constructor(private val avatarRenderer:
}
when (asyncUsers) {
is Incomplete -> renderLoading()
is Success -> renderUsers(asyncUsers(), currentState.selectedUsers)
is Success -> renderUsers(asyncUsers(), currentState.selectedUsers.map { it.userId })
is Fail -> renderFailure(asyncUsers.error)
}
}
@ -79,10 +75,10 @@ class CreateDirectRoomController @Inject constructor(private val avatarRenderer:
}
}

private fun renderUsers(users: List<User>, selectedUsers: Set<User>) {
private fun renderUsers(users: List<User>, selectedUsers: List<String>) {
var lastFirstLetter: String? = null
users.forEach { user ->
val isSelected = selectedUsers.contains(user)
val isSelected = selectedUsers.contains(user.userId)
val currentFirstLetter = user.displayName.firstLetterOfDisplayName()
val showLetter = currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
lastFirstLetter = currentFirstLetter

View File

@ -25,6 +25,7 @@ import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.platform.VectorBaseFragment
import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.*
import javax.inject.Inject
@ -82,6 +83,7 @@ class CreateDirectRoomDirectoryUsersFragment : VectorBaseFragment(), CreateDirec
}

override fun onItemClick(user: User) {
view?.hideKeyboard()
viewModel.handle(CreateDirectRoomActions.SelectUser(user))
navigationViewModel.goTo(CreateDirectRoomActivity.Navigation.Previous)
}

View File

@ -19,21 +19,24 @@
package im.vector.riotx.features.home.createdirect

import android.os.Bundle
import android.text.Spannable
import android.view.MenuItem
import androidx.lifecycle.ViewModelProviders
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.activityViewModel
import com.jakewharton.rxbinding3.appcompat.queryTextChanges
import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.beforeTextChangeEvents
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.matrix.android.api.MatrixPatterns
import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.html.PillImageSpan
import kotlinx.android.synthetic.main.fragment_create_direct_room.*
import kotlinx.android.synthetic.main.fragment_public_rooms.*
import javax.inject.Inject

class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomController.Callback {
@ -45,6 +48,7 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle
private val viewModel: CreateDirectRoomViewModel by activityViewModel()

@Inject lateinit var directRoomController: CreateDirectRoomController
@Inject lateinit var avatarRenderer: AvatarRenderer
private lateinit var navigationViewModel: CreateDirectRoomNavigationViewModel

override fun injectWith(injector: ScreenComponent) {
@ -59,6 +63,10 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle
setupFilterView()
setupAddByMatrixIdView()
setupCloseView()
viewModel.selectUserEvent.observeEvent(this) {
updateFilterViewWith(it)

}
viewModel.subscribe(this) { renderState(it) }
}

@ -90,16 +98,43 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle

private fun setupFilterView() {
createDirectRoomFilter
.queryTextChanges()
.subscribe {
val action = if (it.isNullOrEmpty()) {
.textChanges()
.subscribe { text ->
val userMatches = MatrixPatterns.PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER.findAll(text)
val lastUserMatch = userMatches.lastOrNull()
val filterValue = if (lastUserMatch == null) {
text
} else {
text.substring(startIndex = lastUserMatch.range.endInclusive + 1)
}.trim()

val action = if (filterValue.isBlank()) {
CreateDirectRoomActions.ClearFilterKnownUsers
} else {
CreateDirectRoomActions.FilterKnownUsers(it.toString())
CreateDirectRoomActions.FilterKnownUsers(filterValue.toString())
}
viewModel.handle(action)
}
.disposeOnDestroy()

createDirectRoomFilter
.beforeTextChangeEvents()
.subscribe { event ->
if (event.after == 0) {
val sub = event.text.substring(0, event.start)
val startIndexOfUser = sub.lastIndexOf(" ") + 1
val user = sub.substring(startIndexOfUser)
val selectedUser = withState(viewModel) { state ->
state.selectedUsers.find { it.userId == user }
}
if (selectedUser != null) {
viewModel.handle(CreateDirectRoomActions.RemoveSelectedUser(selectedUser))
}
}
}
.disposeOnDestroy()

createDirectRoomFilter.requestFocus()
}

private fun setupCloseView() {
@ -109,11 +144,37 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle
}

private fun renderState(state: CreateDirectRoomViewState) {

directRoomController.setData(state)
}

private fun updateFilterViewWith(data: SelectUserAction) = withState(viewModel) { state ->
if (state.selectedUsers.isEmpty()) {
createDirectRoomFilter.text = null
} else {
val editable = createDirectRoomFilter.editableText
val user = data.user
if (data.isAdded) {
val startIndex = editable.lastIndexOf(" ") + 1
val endIndex = editable.length
editable.replace(startIndex, endIndex, "${user.userId} ")
val span = PillImageSpan(GlideApp.with(this), avatarRenderer, requireContext(), user.userId, user)
span.bind(createDirectRoomFilter)
editable.setSpan(span, startIndex, startIndex + user.userId.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
} else {
val startIndex = editable.indexOf(user.userId)
if (startIndex != -1) {
var endIndex = editable.indexOf(" ", startIndex) + 1
if (endIndex == 0) {
endIndex = editable.length
}
editable.replace(startIndex, endIndex, "")
}
}
}
}

override fun onItemClick(user: User) {
view?.hideKeyboard()
viewModel.handle(CreateDirectRoomActions.SelectUser(user))
}
}

View File

@ -28,9 +28,7 @@ import com.amulyakhare.textdrawable.TextDrawable
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.getColorFromUserId

@EpoxyModelClass(layout = R.layout.item_create_direct_room_user)
abstract class CreateDirectRoomUserItem : VectorEpoxyModel<CreateDirectRoomUserItem.Holder>() {

View File

@ -18,10 +18,10 @@

package im.vector.riotx.features.home.createdirect

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import arrow.core.Option
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.airbnb.mvrx.*
import com.jakewharton.rxrelay2.BehaviorRelay
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
@ -29,7 +29,9 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.rx.rx
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.LiveEvent
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import java.util.concurrent.TimeUnit
@ -37,6 +39,11 @@ import java.util.concurrent.TimeUnit
private typealias KnowUsersFilter = String
private typealias DirectoryUsersSearch = String

data class SelectUserAction(
val user: User,
val isAdded: Boolean
)

class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
initialState: CreateDirectRoomViewState,
private val session: Session)
@ -50,6 +57,10 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
private val knownUsersFilter = BehaviorRelay.createDefault<Option<KnowUsersFilter>>(Option.empty())
private val directoryUsersSearch = BehaviorRelay.create<DirectoryUsersSearch>()

private val _selectUserEvent = MutableLiveData<LiveEvent<SelectUserAction>>()
val selectUserEvent: LiveData<LiveEvent<SelectUserAction>>
get() = _selectUserEvent

companion object : MvRxViewModelFactory<CreateDirectRoomViewModel, CreateDirectRoomViewState> {

@JvmStatic
@ -78,7 +89,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
private fun createRoomAndInviteSelectedUsers() = withState { currentState ->
val isDirect = currentState.selectedUsers.size == 1
val roomParams = CreateRoomParams().apply {
invitedUserIds = ArrayList(currentState.selectedUsers.map { user -> user.userId })
invitedUserIds = ArrayList(currentState.selectedUsers.map { it.userId })
if (isDirect) {
setDirectMessage()
}
@ -92,33 +103,42 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
}

private fun handleRemoveSelectedUser(action: CreateDirectRoomActions.RemoveSelectedUser) = withState {
val selectedUsers = it.selectedUsers.minusElement(action.user)
val selectedUsers = it.selectedUsers.minus(action.user)
setState { copy(selectedUsers = selectedUsers) }
_selectUserEvent.postLiveEvent(SelectUserAction(action.user, false))
}

private fun handleSelectUser(action: CreateDirectRoomActions.SelectUser) = withState {
val selectedUsers = if (it.selectedUsers.contains(action.user)) {
it.selectedUsers.minusElement(action.user)
//Reset the filter asap
knownUsersFilter.accept(Option.empty())
directoryUsersSearch.accept("")

val isAddOperation: Boolean
val selectedUsers: Set<User>
if (it.selectedUsers.contains(action.user)) {
selectedUsers = it.selectedUsers.minus(action.user)
isAddOperation = false
} else {
it.selectedUsers.plus(action.user)
selectedUsers = it.selectedUsers.plus(action.user)
isAddOperation = true
}
setState { copy(selectedUsers = selectedUsers) }
_selectUserEvent.postLiveEvent(SelectUserAction(action.user, isAddOperation))
}

private fun observeDirectoryUsers() {
directoryUsersSearch
.throttleLast(300, TimeUnit.MILLISECONDS)
.throttleLast(500, TimeUnit.MILLISECONDS)
.switchMapSingle { search ->
session.rx()
.searchUsersDirectory(search, 50, emptySet())
.map { users ->
users.sortedBy { it.displayName }
}
.toAsync { copy(directoryUsers = it) }
}
.execute { async ->
copy(directoryUsers = async)
}

.subscribe()
.disposeOnClear()
}

private fun observeKnownUsers() {
@ -133,7 +153,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
} else {
users.filter {
it.displayName?.contains(filterValue, ignoreCase = true) ?: false
|| it.userId.contains(filterValue, ignoreCase = true)
|| it.userId.contains(filterValue, ignoreCase = true)
}
}
}

View File

@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.rx.rx
import im.vector.riotx.R
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.LiveEvent
@ -67,7 +68,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
private fun observeSelectionState() {
selectSubscribe(GroupListViewState::selectedGroup) {
if (it != null) {
_openGroupLiveData.postValue(LiveEvent(it))
_openGroupLiveData.postLiveEvent(it)
val optionGroup = Option.fromNullable(it)
selectedGroupHolder.post(optionGroup)
}

View File

@ -40,6 +40,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.rx.rx
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.intent.getFilenameFromUri
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.UserPreferencesProvider
@ -168,62 +169,62 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
is ParsedCommand.ErrorNotACommand -> {
// Send the text message to the room
room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
}
is ParsedCommand.ErrorSyntax -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command)))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command))
}
is ParsedCommand.ErrorEmptySlashCommand -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown("/")))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandUnknown("/"))
}
is ParsedCommand.ErrorUnknownSlashCommand -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand)))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand))
}
is ParsedCommand.Invite -> {
handleInviteSlashCommand(slashCommandResult)
}
is ParsedCommand.SetUserPowerLevel -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
}
is ParsedCommand.ClearScalarToken -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
}
is ParsedCommand.SetMarkdown -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
}
is ParsedCommand.UnbanUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
}
is ParsedCommand.BanUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
}
is ParsedCommand.KickUser -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
}
is ParsedCommand.JoinRoom -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
}
is ParsedCommand.PartRoom -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
}
is ParsedCommand.SendEmote -> {
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled)
}
is ParsedCommand.ChangeTopic -> {
handleChangeTopicSlashCommand(slashCommandResult)
}
is ParsedCommand.ChangeDisplayName -> {
// TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
}
}
}
@ -255,7 +256,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
sendMode = SendMode.REGULAR
)
}
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
}
is SendMode.QUOTE -> {
val messageContent: MessageContent? =
@ -280,7 +281,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
sendMode = SendMode.REGULAR
)
}
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
}
is SendMode.REPLY -> {
state.sendMode.timelineEvent.let {
@ -290,7 +291,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
sendMode = SendMode.REGULAR
)
}
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
}

}
@ -319,29 +320,29 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
}

private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled)

room.updateTopic(changeTopic.topic, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandResultOk))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultOk)
}

override fun onFailure(failure: Throwable) {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandResultError(failure)))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultError(failure))
}
})
}

private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled)

room.invite(invite.userId, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandResultOk))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultOk)
}

override fun onFailure(failure: Throwable) {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandResultError(failure)))
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultError(failure))
}
})
}
@ -453,19 +454,19 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
action.messageFileContent.encryptedFileInfo?.toElementToDecrypt(),
object : MatrixCallback<File> {
override fun onSuccess(data: File) {
_downloadedFileEvent.postValue(LiveEvent(DownloadFileState(
_downloadedFileEvent.postLiveEvent(DownloadFileState(
action.messageFileContent.getMimeType(),
data,
null
)))
))
}

override fun onFailure(failure: Throwable) {
_downloadedFileEvent.postValue(LiveEvent(DownloadFileState(
_downloadedFileEvent.postLiveEvent(DownloadFileState(
action.messageFileContent.getMimeType(),
null,
failure
)))
))
}
})

@ -494,7 +495,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
}
}

_navigateToEvent.postValue(LiveEvent(targetEventId))
_navigateToEvent.postLiveEvent(targetEventId)
} else {
// change timeline
timeline.dispose()
@ -519,7 +520,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
}
}

_navigateToEvent.postValue(LiveEvent(targetEventId))
_navigateToEvent.postLiveEvent(targetEventId)
}
}


View File

@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.LiveEvent
import im.vector.riotx.features.home.HomeRoomListObservableStore
@ -142,7 +143,7 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room

override fun onFailure(failure: Throwable) {
// Notify the user
_invitationAnswerErrorLiveData.postValue(LiveEvent(failure))
_invitationAnswerErrorLiveData.postLiveEvent(failure)

setState {
copy(
@ -178,7 +179,7 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room

override fun onFailure(failure: Throwable) {
// Notify the user
_invitationAnswerErrorLiveData.postValue(LiveEvent(failure))
_invitationAnswerErrorLiveData.postLiveEvent(failure)

setState {
copy(

View File

@ -31,6 +31,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRooms
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.rx.rx
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.LiveEvent
import timber.log.Timber
@ -207,7 +208,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:

override fun onFailure(failure: Throwable) {
// Notify the user
_joinRoomErrorLiveData.postValue(LiveEvent(failure))
_joinRoomErrorLiveData.postLiveEvent(failure)

setState {
copy(

View File

@ -71,16 +71,13 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/createDirectRoomToolbar">

<androidx.appcompat.widget.SearchView
<EditText
android:id="@+id/createDirectRoomFilter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text|textMultiLine"
app:closeIcon="@drawable/ic_x_green"
app:iconifiedByDefault="false"
app:queryBackground="@android:color/transparent"
app:queryHint="@string/room_directory_search_hint"
app:searchIcon="@drawable/ic_filter" />
android:maxHeight="80dp"
android:importantForAutofill="no"
android:hint="@string/room_directory_search_hint"/>

</androidx.cardview.widget.CardView>