2019-01-18 10:12:08 +00:00
|
|
|
/*
|
2019-01-25 13:04:59 +00:00
|
|
|
* Copyright 2019 New Vector Ltd
|
2019-01-18 10:12:08 +00:00
|
|
|
*
|
2019-01-25 13:04:59 +00:00
|
|
|
* 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
|
2019-01-18 10:12:08 +00:00
|
|
|
*
|
2019-01-25 13:04:59 +00:00
|
|
|
* 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.
|
2019-01-18 10:12:08 +00:00
|
|
|
*/
|
|
|
|
|
2018-12-29 16:54:03 +00:00
|
|
|
package im.vector.riotredesign.features.home.room.detail
|
|
|
|
|
2019-04-09 11:36:33 +00:00
|
|
|
import androidx.lifecycle.LiveData
|
|
|
|
import androidx.lifecycle.MutableLiveData
|
2018-12-29 16:54:03 +00:00
|
|
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
2019-05-13 17:23:02 +00:00
|
|
|
import com.airbnb.mvrx.Success
|
2019-01-16 18:25:43 +00:00
|
|
|
import com.airbnb.mvrx.ViewModelContext
|
2019-01-30 17:39:54 +00:00
|
|
|
import com.jakewharton.rxrelay2.BehaviorRelay
|
2018-12-29 16:54:03 +00:00
|
|
|
import im.vector.matrix.android.api.MatrixCallback
|
|
|
|
import im.vector.matrix.android.api.session.Session
|
2019-04-04 17:55:58 +00:00
|
|
|
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
2019-05-13 17:23:02 +00:00
|
|
|
import im.vector.matrix.android.api.session.room.model.Membership
|
2019-04-09 15:53:23 +00:00
|
|
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
2018-12-29 16:54:03 +00:00
|
|
|
import im.vector.matrix.rx.rx
|
2019-05-21 12:12:18 +00:00
|
|
|
import im.vector.riotredesign.R
|
2019-04-05 08:40:59 +00:00
|
|
|
import im.vector.riotredesign.core.platform.VectorViewModel
|
2019-04-09 11:36:33 +00:00
|
|
|
import im.vector.riotredesign.core.utils.LiveEvent
|
|
|
|
import im.vector.riotredesign.features.command.CommandParser
|
|
|
|
import im.vector.riotredesign.features.command.ParsedCommand
|
2019-02-19 10:16:31 +00:00
|
|
|
import im.vector.riotredesign.features.home.room.VisibleRoomStore
|
2019-03-21 19:21:45 +00:00
|
|
|
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
|
2019-01-30 17:39:54 +00:00
|
|
|
import io.reactivex.rxkotlin.subscribeBy
|
2019-01-11 11:07:38 +00:00
|
|
|
import org.koin.android.ext.android.get
|
2019-05-21 12:12:18 +00:00
|
|
|
import java.text.SimpleDateFormat
|
|
|
|
import java.util.*
|
2019-01-30 17:39:54 +00:00
|
|
|
import java.util.concurrent.TimeUnit
|
2018-12-29 16:54:03 +00:00
|
|
|
|
|
|
|
class RoomDetailViewModel(initialState: RoomDetailViewState,
|
2019-01-11 11:07:38 +00:00
|
|
|
private val session: Session,
|
2019-02-19 10:16:31 +00:00
|
|
|
private val visibleRoomHolder: VisibleRoomStore
|
2019-04-05 08:40:59 +00:00
|
|
|
) : VectorViewModel<RoomDetailViewState>(initialState) {
|
2018-12-29 16:54:03 +00:00
|
|
|
|
|
|
|
private val room = session.getRoom(initialState.roomId)!!
|
|
|
|
private val roomId = initialState.roomId
|
|
|
|
private val eventId = initialState.eventId
|
2019-01-30 17:39:54 +00:00
|
|
|
private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>()
|
2019-03-21 19:21:45 +00:00
|
|
|
private val timeline = room.createTimeline(eventId, TimelineDisplayableEvents.DISPLAYABLE_TYPES)
|
2019-01-30 17:39:54 +00:00
|
|
|
|
2019-01-16 18:25:43 +00:00
|
|
|
companion object : MvRxViewModelFactory<RoomDetailViewModel, RoomDetailViewState> {
|
2018-12-29 16:54:03 +00:00
|
|
|
|
2019-03-21 19:21:45 +00:00
|
|
|
const val PAGINATION_COUNT = 50
|
|
|
|
|
2018-12-29 16:54:03 +00:00
|
|
|
@JvmStatic
|
2019-01-16 18:25:43 +00:00
|
|
|
override fun create(viewModelContext: ViewModelContext, state: RoomDetailViewState): RoomDetailViewModel? {
|
2019-02-28 17:50:30 +00:00
|
|
|
val currentSession = viewModelContext.activity.get<Session>()
|
2019-02-19 10:16:31 +00:00
|
|
|
val visibleRoomHolder = viewModelContext.activity.get<VisibleRoomStore>()
|
2019-01-11 11:07:38 +00:00
|
|
|
return RoomDetailViewModel(state, currentSession, visibleRoomHolder)
|
2018-12-29 16:54:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
init {
|
|
|
|
observeRoomSummary()
|
2019-03-21 19:21:45 +00:00
|
|
|
observeEventDisplayedActions()
|
2019-05-13 17:23:02 +00:00
|
|
|
observeInvitationState()
|
2018-12-29 16:54:03 +00:00
|
|
|
room.loadRoomMembersIfNeeded()
|
2019-03-15 18:27:56 +00:00
|
|
|
timeline.start()
|
|
|
|
setState { copy(timeline = this@RoomDetailViewModel.timeline) }
|
2018-12-29 16:54:03 +00:00
|
|
|
}
|
|
|
|
|
2019-01-30 17:39:54 +00:00
|
|
|
fun process(action: RoomDetailActions) {
|
2018-12-29 16:54:03 +00:00
|
|
|
when (action) {
|
2019-05-20 10:43:02 +00:00
|
|
|
is RoomDetailActions.SendMessage -> handleSendMessage(action)
|
|
|
|
is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
|
|
|
|
is RoomDetailActions.SendMedia -> handleSendMedia(action)
|
2019-01-30 17:39:54 +00:00
|
|
|
is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action)
|
2019-05-20 10:43:02 +00:00
|
|
|
is RoomDetailActions.LoadMore -> handleLoadMore(action)
|
|
|
|
is RoomDetailActions.SendReaction -> handleSendReaction(action)
|
|
|
|
is RoomDetailActions.AcceptInvite -> handleAcceptInvite()
|
|
|
|
is RoomDetailActions.RejectInvite -> handleRejectInvite()
|
2019-05-17 15:15:44 +00:00
|
|
|
is RoomDetailActions.RedactAction -> handleRedactEvent(action)
|
2019-05-20 10:43:02 +00:00
|
|
|
is RoomDetailActions.UndoReaction -> handleUndoReact(action)
|
|
|
|
is RoomDetailActions.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
|
2019-05-21 12:12:18 +00:00
|
|
|
is RoomDetailActions.ShowEditHistoryAction -> handleShowEditHistoryReaction(action)
|
2018-12-29 16:54:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-20 10:43:02 +00:00
|
|
|
|
2019-05-21 12:12:18 +00:00
|
|
|
private val _nonBlockingPopAlert = MutableLiveData<LiveEvent<Pair<Int, List<Any>>>>()
|
|
|
|
val nonBlockingPopAlert: LiveData<LiveEvent<Pair<Int, List<Any>>>>
|
|
|
|
get() = _nonBlockingPopAlert
|
|
|
|
|
|
|
|
|
2019-04-09 11:36:33 +00:00
|
|
|
private val _sendMessageResultLiveData = MutableLiveData<LiveEvent<SendMessageResult>>()
|
|
|
|
val sendMessageResultLiveData: LiveData<LiveEvent<SendMessageResult>>
|
|
|
|
get() = _sendMessageResultLiveData
|
|
|
|
|
2018-12-29 16:54:03 +00:00
|
|
|
// PRIVATE METHODS *****************************************************************************
|
|
|
|
|
|
|
|
private fun handleSendMessage(action: RoomDetailActions.SendMessage) {
|
2019-04-09 11:36:33 +00:00
|
|
|
// Handle slash command
|
|
|
|
val slashCommandResult = CommandParser.parseSplashCommand(action.text)
|
|
|
|
|
|
|
|
when (slashCommandResult) {
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.ErrorNotACommand -> {
|
2019-04-09 11:36:33 +00:00
|
|
|
// Send the text message to the room
|
2019-04-11 11:21:51 +00:00
|
|
|
room.sendTextMessage(action.text)
|
2019-04-09 11:36:33 +00:00
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.MessageSent))
|
|
|
|
}
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.ErrorSyntax -> {
|
2019-04-09 11:36:33 +00:00
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandError(slashCommandResult.command)))
|
|
|
|
}
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.ErrorEmptySlashCommand -> {
|
2019-04-09 11:36:33 +00:00
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown("/")))
|
|
|
|
}
|
|
|
|
is ParsedCommand.ErrorUnknownSlashCommand -> {
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandUnknown(slashCommandResult.slashCommand)))
|
|
|
|
}
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.Invite -> {
|
2019-04-09 12:35:18 +00:00
|
|
|
handleInviteSlashCommand(slashCommandResult)
|
|
|
|
}
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.SetUserPowerLevel -> {
|
2019-04-09 13:21:17 +00:00
|
|
|
// TODO
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
|
|
|
}
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.ClearScalarToken -> {
|
2019-04-09 13:21:17 +00:00
|
|
|
// TODO
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
|
|
|
}
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.SetMarkdown -> {
|
2019-04-09 13:21:17 +00:00
|
|
|
// TODO
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
|
|
|
}
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.UnbanUser -> {
|
2019-04-09 13:21:17 +00:00
|
|
|
// TODO
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
|
|
|
}
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.BanUser -> {
|
2019-04-09 13:21:17 +00:00
|
|
|
// TODO
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
|
|
|
}
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.KickUser -> {
|
2019-04-09 13:21:17 +00:00
|
|
|
// TODO
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
|
|
|
}
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.JoinRoom -> {
|
2019-04-09 13:21:17 +00:00
|
|
|
// TODO
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
|
|
|
}
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.PartRoom -> {
|
2019-04-09 13:21:17 +00:00
|
|
|
// TODO
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
|
|
|
}
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.SendEmote -> {
|
2019-04-11 11:21:51 +00:00
|
|
|
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
|
2019-04-09 15:53:23 +00:00
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
|
2019-04-09 13:21:17 +00:00
|
|
|
}
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.ChangeTopic -> {
|
2019-04-09 16:33:28 +00:00
|
|
|
handleChangeTopicSlashCommand(slashCommandResult)
|
2019-04-09 13:21:17 +00:00
|
|
|
}
|
2019-05-20 10:43:02 +00:00
|
|
|
is ParsedCommand.ChangeDisplayName -> {
|
2019-04-09 13:21:17 +00:00
|
|
|
// TODO
|
2019-04-09 12:35:18 +00:00
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandNotImplemented))
|
2019-04-09 11:36:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-21 12:12:18 +00:00
|
|
|
private fun handleShowEditHistoryReaction(action: RoomDetailActions.ShowEditHistoryAction) {
|
|
|
|
//TODO temporary implementation
|
|
|
|
val lastReplace = action.editAggregatedSummary.sourceEvents.lastOrNull()?.let {
|
|
|
|
room.getTimeLineEvent(it)
|
|
|
|
} ?: return
|
|
|
|
|
|
|
|
val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault())
|
|
|
|
_nonBlockingPopAlert.postValue(LiveEvent(
|
|
|
|
Pair(R.string.last_edited_info_message, listOf(
|
|
|
|
lastReplace.senderName ?: "?",
|
|
|
|
dateFormat.format(Date(lastReplace.root.originServerTs ?: 0)))
|
|
|
|
))
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-04-09 16:33:28 +00:00
|
|
|
private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
|
|
|
|
|
|
|
|
room.updateTopic(changeTopic.topic, object : MatrixCallback<Unit> {
|
|
|
|
override fun onSuccess(data: Unit) {
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandResultOk))
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onFailure(failure: Throwable) {
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandResultError(failure)))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-04-09 12:35:18 +00:00
|
|
|
private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
|
|
|
|
|
|
|
|
room.invite(invite.userId, object : MatrixCallback<Unit> {
|
|
|
|
override fun onSuccess(data: Unit) {
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandResultOk))
|
2019-04-09 11:36:33 +00:00
|
|
|
}
|
2019-04-09 12:35:18 +00:00
|
|
|
|
|
|
|
override fun onFailure(failure: Throwable) {
|
|
|
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandResultError(failure)))
|
2019-04-09 11:36:33 +00:00
|
|
|
}
|
2019-04-09 12:35:18 +00:00
|
|
|
})
|
2018-12-29 16:54:03 +00:00
|
|
|
}
|
|
|
|
|
2019-05-16 14:35:54 +00:00
|
|
|
|
|
|
|
private fun handleSendReaction(action: RoomDetailActions.SendReaction) {
|
2019-05-17 15:38:09 +00:00
|
|
|
room.sendReaction(action.reaction, action.targetEventId)
|
2019-05-16 14:35:54 +00:00
|
|
|
}
|
|
|
|
|
2019-05-17 15:15:44 +00:00
|
|
|
private fun handleRedactEvent(action: RoomDetailActions.RedactAction) {
|
|
|
|
val event = room.getTimeLineEvent(action.targetEventId) ?: return
|
|
|
|
room.redactEvent(event.root, action.reason)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handleUndoReact(action: RoomDetailActions.UndoReaction) {
|
|
|
|
room.undoReaction(action.key, action.targetEventId, session.sessionParams.credentials.userId)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-05-20 10:43:02 +00:00
|
|
|
private fun handleUpdateQuickReaction(action: RoomDetailActions.UpdateQuickReactAction) {
|
|
|
|
room.updateQuickReaction(action.selectedReaction, action.opposite, action.targetEventId, session.sessionParams.credentials.userId)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-04-03 20:54:48 +00:00
|
|
|
private fun handleSendMedia(action: RoomDetailActions.SendMedia) {
|
2019-04-09 17:57:43 +00:00
|
|
|
val attachments = action.mediaFiles.map {
|
|
|
|
ContentAttachmentData(
|
|
|
|
size = it.size,
|
|
|
|
duration = it.duration,
|
|
|
|
date = it.date,
|
|
|
|
height = it.height,
|
|
|
|
width = it.width,
|
|
|
|
name = it.name,
|
|
|
|
path = it.path,
|
|
|
|
mimeType = it.mimeType,
|
|
|
|
type = ContentAttachmentData.Type.values()[it.mediaType]
|
|
|
|
)
|
|
|
|
}
|
|
|
|
room.sendMedias(attachments)
|
2019-04-03 20:54:48 +00:00
|
|
|
}
|
|
|
|
|
2019-01-30 17:39:54 +00:00
|
|
|
private fun handleEventDisplayed(action: RoomDetailActions.EventDisplayed) {
|
|
|
|
displayedEventsObservable.accept(action)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handleIsDisplayed() {
|
2019-02-19 10:16:31 +00:00
|
|
|
visibleRoomHolder.post(roomId)
|
2019-01-30 17:39:54 +00:00
|
|
|
}
|
|
|
|
|
2019-03-19 18:45:32 +00:00
|
|
|
private fun handleLoadMore(action: RoomDetailActions.LoadMore) {
|
2019-03-21 19:21:45 +00:00
|
|
|
timeline.paginate(action.direction, PAGINATION_COUNT)
|
2019-03-19 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
2019-05-07 17:33:58 +00:00
|
|
|
private fun handleRejectInvite() {
|
|
|
|
room.leave(object : MatrixCallback<Unit> {})
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handleAcceptInvite() {
|
|
|
|
room.join(object : MatrixCallback<Unit> {})
|
|
|
|
}
|
|
|
|
|
2019-05-20 10:43:02 +00:00
|
|
|
|
2019-03-21 19:21:45 +00:00
|
|
|
private fun observeEventDisplayedActions() {
|
2019-01-30 17:39:54 +00:00
|
|
|
// We are buffering scroll events for one second
|
|
|
|
// and keep the most recent one to set the read receipt on.
|
2019-03-15 18:27:56 +00:00
|
|
|
displayedEventsObservable
|
2019-01-30 17:39:54 +00:00
|
|
|
.buffer(1, TimeUnit.SECONDS)
|
|
|
|
.filter { it.isNotEmpty() }
|
2019-01-31 10:45:11 +00:00
|
|
|
.subscribeBy(onNext = { actions ->
|
2019-03-15 18:27:56 +00:00
|
|
|
val mostRecentEvent = actions.maxBy { it.event.displayIndex }
|
2019-01-30 17:39:54 +00:00
|
|
|
mostRecentEvent?.event?.root?.eventId?.let { eventId ->
|
2019-04-03 14:36:45 +00:00
|
|
|
room.setReadReceipt(eventId, callback = object : MatrixCallback<Unit> {})
|
2019-01-30 17:39:54 +00:00
|
|
|
}
|
2019-01-31 10:45:11 +00:00
|
|
|
})
|
2019-01-30 17:39:54 +00:00
|
|
|
.disposeOnClear()
|
|
|
|
}
|
|
|
|
|
2018-12-29 16:54:03 +00:00
|
|
|
private fun observeRoomSummary() {
|
|
|
|
room.rx().liveRoomSummary()
|
|
|
|
.execute { async ->
|
|
|
|
copy(asyncRoomSummary = async)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-13 17:23:02 +00:00
|
|
|
private fun observeInvitationState() {
|
|
|
|
asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary ->
|
|
|
|
if (summary.membership == Membership.INVITE) {
|
|
|
|
summary.lastMessage?.sender?.let { senderId ->
|
|
|
|
session.getUser(senderId)
|
|
|
|
}?.also {
|
|
|
|
setState { copy(inviter = Success(it)) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-15 18:27:56 +00:00
|
|
|
override fun onCleared() {
|
|
|
|
timeline.dispose()
|
2019-03-19 18:45:32 +00:00
|
|
|
super.onCleared()
|
2018-12-29 16:54:03 +00:00
|
|
|
}
|
2019-04-09 17:57:43 +00:00
|
|
|
|
2018-12-29 16:54:03 +00:00
|
|
|
}
|