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. // regex pattern to find matrix user ids in a string.
// See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids // 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 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. // regex pattern to find room ids in a string.
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX" 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.LifecycleOwner
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import im.vector.riotx.core.utils.FirstThrottler import im.vector.riotx.core.utils.FirstThrottler
import im.vector.riotx.core.utils.EventObserver 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.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent


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




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

View File

@ -16,20 +16,36 @@


package im.vector.riotx.core.platform package im.vector.riotx.core.platform


import com.airbnb.mvrx.BaseMvRxViewModel import com.airbnb.mvrx.*
import com.airbnb.mvrx.MvRxState
import im.vector.matrix.android.api.util.CancelableBag import im.vector.matrix.android.api.util.CancelableBag
import im.vector.riotx.BuildConfig 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) abstract class VectorViewModel<S : MvRxState>(initialState: S)
: BaseMvRxViewModel<S>(initialState, false) { : BaseMvRxViewModel<S>(initialState, false) {


protected val cancelableBag = CancelableBag() /**

* This method does the same thing as the execute function, but it doesn't subscribe to the stream
override fun onCleared() { * so you can use this in a switchMap or a flatMap
super.onCleared() */
cancelableBag.cancel() 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 package im.vector.riotx.features.home.createdirect


import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyController
import com.airbnb.epoxy.VisibilityState
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success 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.api.session.user.model.User
import im.vector.matrix.android.internal.util.firstLetterOfDisplayName import im.vector.matrix.android.internal.util.firstLetterOfDisplayName
import im.vector.riotx.core.epoxy.errorWithRetryItem import im.vector.riotx.core.epoxy.errorWithRetryItem
@ -60,7 +56,7 @@ class CreateDirectRoomController @Inject constructor(private val avatarRenderer:
} }
when (asyncUsers) { when (asyncUsers) {
is Incomplete -> renderLoading() is Incomplete -> renderLoading()
is Success -> renderUsers(asyncUsers(), currentState.selectedUsers) is Success -> renderUsers(asyncUsers(), currentState.selectedUsers.map { it.userId })
is Fail -> renderFailure(asyncUsers.error) 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 var lastFirstLetter: String? = null
users.forEach { user -> users.forEach { user ->
val isSelected = selectedUsers.contains(user) val isSelected = selectedUsers.contains(user.userId)
val currentFirstLetter = user.displayName.firstLetterOfDisplayName() val currentFirstLetter = user.displayName.firstLetterOfDisplayName()
val showLetter = currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter val showLetter = currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
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.matrix.android.api.session.user.model.User
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.* import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.*
import javax.inject.Inject import javax.inject.Inject
@ -82,6 +83,7 @@ class CreateDirectRoomDirectoryUsersFragment : VectorBaseFragment(), CreateDirec
} }


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

View File

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


import android.os.Bundle import android.os.Bundle
import android.text.Spannable
import android.view.MenuItem import android.view.MenuItem
import androidx.lifecycle.ViewModelProviders 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.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.matrix.android.api.session.user.model.User
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent 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.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_create_direct_room.*
import kotlinx.android.synthetic.main.fragment_public_rooms.*
import javax.inject.Inject import javax.inject.Inject


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


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


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

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


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


private fun setupFilterView() { private fun setupFilterView() {
createDirectRoomFilter createDirectRoomFilter
.queryTextChanges() .textChanges()
.subscribe { .subscribe { text ->
val action = if (it.isNullOrEmpty()) { 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 CreateDirectRoomActions.ClearFilterKnownUsers
} else { } else {
CreateDirectRoomActions.FilterKnownUsers(it.toString()) CreateDirectRoomActions.FilterKnownUsers(filterValue.toString())
} }
viewModel.handle(action) viewModel.handle(action)
} }
.disposeOnDestroy() .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() { private fun setupCloseView() {
@ -109,11 +144,37 @@ class CreateDirectRoomFragment : VectorBaseFragment(), CreateDirectRoomControlle
} }


private fun renderState(state: CreateDirectRoomViewState) { private fun renderState(state: CreateDirectRoomViewState) {

directRoomController.setData(state) 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) { override fun onItemClick(user: User) {
view?.hideKeyboard()
viewModel.handle(CreateDirectRoomActions.SelectUser(user)) 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.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel 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.AvatarRenderer
import im.vector.riotx.features.home.getColorFromUserId


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

View File

@ -18,10 +18,10 @@


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


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


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

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


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

companion object : MvRxViewModelFactory<CreateDirectRoomViewModel, CreateDirectRoomViewState> { companion object : MvRxViewModelFactory<CreateDirectRoomViewModel, CreateDirectRoomViewState> {


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


private fun handleRemoveSelectedUser(action: CreateDirectRoomActions.RemoveSelectedUser) = withState { 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) } setState { copy(selectedUsers = selectedUsers) }
_selectUserEvent.postLiveEvent(SelectUserAction(action.user, false))
} }


private fun handleSelectUser(action: CreateDirectRoomActions.SelectUser) = withState { private fun handleSelectUser(action: CreateDirectRoomActions.SelectUser) = withState {
val selectedUsers = if (it.selectedUsers.contains(action.user)) { //Reset the filter asap
it.selectedUsers.minusElement(action.user) 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 { } else {
it.selectedUsers.plus(action.user) selectedUsers = it.selectedUsers.plus(action.user)
isAddOperation = true
} }
setState { copy(selectedUsers = selectedUsers) } setState { copy(selectedUsers = selectedUsers) }
_selectUserEvent.postLiveEvent(SelectUserAction(action.user, isAddOperation))
} }


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

} }


private fun observeKnownUsers() { private fun observeKnownUsers() {
@ -133,7 +153,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
} else { } else {
users.filter { users.filter {
it.displayName?.contains(filterValue, ignoreCase = true) ?: false 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.android.api.session.group.model.GroupSummary
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent
@ -67,7 +68,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
private fun observeSelectionState() { private fun observeSelectionState() {
selectSubscribe(GroupListViewState::selectedGroup) { selectSubscribe(GroupListViewState::selectedGroup) {
if (it != null) { if (it != null) {
_openGroupLiveData.postValue(LiveEvent(it)) _openGroupLiveData.postLiveEvent(it)
val optionGroup = Option.fromNullable(it) val optionGroup = Option.fromNullable(it)
selectedGroupHolder.post(optionGroup) 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.attachments.toElementToDecrypt
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.rx.rx 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.intent.getFilenameFromUri
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.UserPreferencesProvider import im.vector.riotx.core.resources.UserPreferencesProvider
@ -168,62 +169,62 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
is ParsedCommand.ErrorNotACommand -> { is ParsedCommand.ErrorNotACommand -> {
// Send the text message to the room // Send the text message to the room
room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent)) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
} }
is ParsedCommand.ErrorSyntax -> { is ParsedCommand.ErrorSyntax -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command))) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command))
} }
is ParsedCommand.ErrorEmptySlashCommand -> { is ParsedCommand.ErrorEmptySlashCommand -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown("/"))) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandUnknown("/"))
} }
is ParsedCommand.ErrorUnknownSlashCommand -> { is ParsedCommand.ErrorUnknownSlashCommand -> {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand))) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand))
} }
is ParsedCommand.Invite -> { is ParsedCommand.Invite -> {
handleInviteSlashCommand(slashCommandResult) handleInviteSlashCommand(slashCommandResult)
} }
is ParsedCommand.SetUserPowerLevel -> { is ParsedCommand.SetUserPowerLevel -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
} }
is ParsedCommand.ClearScalarToken -> { is ParsedCommand.ClearScalarToken -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
} }
is ParsedCommand.SetMarkdown -> { is ParsedCommand.SetMarkdown -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
} }
is ParsedCommand.UnbanUser -> { is ParsedCommand.UnbanUser -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
} }
is ParsedCommand.BanUser -> { is ParsedCommand.BanUser -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
} }
is ParsedCommand.KickUser -> { is ParsedCommand.KickUser -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
} }
is ParsedCommand.JoinRoom -> { is ParsedCommand.JoinRoom -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
} }
is ParsedCommand.PartRoom -> { is ParsedCommand.PartRoom -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
} }
is ParsedCommand.SendEmote -> { is ParsedCommand.SendEmote -> {
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE) room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled)) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled)
} }
is ParsedCommand.ChangeTopic -> { is ParsedCommand.ChangeTopic -> {
handleChangeTopicSlashCommand(slashCommandResult) handleChangeTopicSlashCommand(slashCommandResult)
} }
is ParsedCommand.ChangeDisplayName -> { is ParsedCommand.ChangeDisplayName -> {
// TODO // TODO
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented)) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
} }
} }
} }
@ -255,7 +256,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
sendMode = SendMode.REGULAR sendMode = SendMode.REGULAR
) )
} }
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent)) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
} }
is SendMode.QUOTE -> { is SendMode.QUOTE -> {
val messageContent: MessageContent? = val messageContent: MessageContent? =
@ -280,7 +281,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
sendMode = SendMode.REGULAR sendMode = SendMode.REGULAR
) )
} }
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent)) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.MessageSent)
} }
is SendMode.REPLY -> { is SendMode.REPLY -> {
state.sendMode.timelineEvent.let { state.sendMode.timelineEvent.let {
@ -290,7 +291,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
sendMode = SendMode.REGULAR 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) { private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled)) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled)


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


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


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


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


override fun onFailure(failure: Throwable) { 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(), action.messageFileContent.encryptedFileInfo?.toElementToDecrypt(),
object : MatrixCallback<File> { object : MatrixCallback<File> {
override fun onSuccess(data: File) { override fun onSuccess(data: File) {
_downloadedFileEvent.postValue(LiveEvent(DownloadFileState( _downloadedFileEvent.postLiveEvent(DownloadFileState(
action.messageFileContent.getMimeType(), action.messageFileContent.getMimeType(),
data, data,
null null
))) ))
} }


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


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


_navigateToEvent.postValue(LiveEvent(targetEventId)) _navigateToEvent.postLiveEvent(targetEventId)
} else { } else {
// change timeline // change timeline
timeline.dispose() 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.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag 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.platform.VectorViewModel
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent
import im.vector.riotx.features.home.HomeRoomListObservableStore import im.vector.riotx.features.home.HomeRoomListObservableStore
@ -142,7 +143,7 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room


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


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


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


setState { setState {
copy( 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.session.room.model.thirdparty.RoomDirectoryData
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.rx.rx 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.platform.VectorViewModel
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent
import timber.log.Timber import timber.log.Timber
@ -207,7 +208,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:


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


setState { setState {
copy( copy(

View File

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


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


</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>