Message states: makes sure the actions bottom sheet is updated with synced event
This commit is contained in:
parent
fa40667633
commit
fad4140924
|
@ -36,9 +36,23 @@ interface TimelineService {
|
||||||
*/
|
*/
|
||||||
fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline
|
fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a snapshot of TimelineEvent event with eventId.
|
||||||
|
* At the opposite of getTimeLineEventLive which will be updated when local echo event is synced, it will return null in this case.
|
||||||
|
* @param eventId the eventId to get the TimelineEvent
|
||||||
|
*/
|
||||||
fun getTimeLineEvent(eventId: String): TimelineEvent?
|
fun getTimeLineEvent(eventId: String): TimelineEvent?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a LiveData of Optional TimelineEvent event with eventId.
|
||||||
|
* If the eventId is a local echo eventId, it will make the LiveData be updated with the synced TimelineEvent when coming through the sync.
|
||||||
|
* In this case, makes sure to use the new synced eventId from the TimelineEvent class if you want to interact, as the local echo is removed from the SDK.
|
||||||
|
* @param eventId the eventId to listen for TimelineEvent
|
||||||
|
*/
|
||||||
fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>>
|
fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a snapshot list of TimelineEvent with EventType.MESSAGE and MessageType.MSGTYPE_IMAGE or MessageType.MSGTYPE_VIDEO.
|
||||||
|
*/
|
||||||
fun getAttachmentMessages(): List<TimelineEvent>
|
fun getAttachmentMessages(): List<TimelineEvent>
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package org.matrix.android.sdk.internal.crypto.tasks
|
package org.matrix.android.sdk.internal.crypto.tasks
|
||||||
|
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
|
@ -24,6 +27,7 @@ import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTa
|
||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||||
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import java.lang.IllegalStateException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface SendEventTask : Task<SendEventTask.Params, String> {
|
internal interface SendEventTask : Task<SendEventTask.Params, String> {
|
||||||
|
@ -51,7 +55,9 @@ internal class DefaultSendEventTask @Inject constructor(
|
||||||
|
|
||||||
val event = handleEncryption(params)
|
val event = handleEncryption(params)
|
||||||
val localId = event.eventId!!
|
val localId = event.eventId!!
|
||||||
|
if((event.content?.get("body") as? String)?.contains("Fail").orFalse()){
|
||||||
|
throw IllegalStateException()
|
||||||
|
}
|
||||||
localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENDING)
|
localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENDING)
|
||||||
val executeRequest = executeRequest<SendResponse>(globalErrorReceiver) {
|
val executeRequest = executeRequest<SendResponse>(globalErrorReceiver) {
|
||||||
apiCall = roomAPI.send(
|
apiCall = roomAPI.send(
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.timeline
|
package org.matrix.android.sdk.internal.session.room.timeline
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
|
@ -31,7 +30,6 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.api.util.toOptional
|
|
||||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||||
import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper
|
import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper
|
||||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||||
|
@ -89,13 +87,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> {
|
override fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> {
|
||||||
val liveData = monarchy.findAllMappedWithChanges(
|
return LiveTimelineEvent(timelineInput, monarchy, taskExecutor, timelineEventMapper, roomId, eventId)
|
||||||
{ TimelineEventEntity.where(it, roomId = roomId, eventId = eventId) },
|
|
||||||
{ timelineEventMapper.map(it) }
|
|
||||||
)
|
|
||||||
return Transformations.map(liveData) { events ->
|
|
||||||
events.firstOrNull().toOptional()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAttachmentMessages(): List<TimelineEvent> {
|
override fun getAttachmentMessages(): List<TimelineEvent> {
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.timeline
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MediatorLiveData
|
||||||
|
import androidx.lifecycle.Transformations
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
import org.matrix.android.sdk.api.util.toOptional
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||||
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class takes care of handling case where local echo is replaced by the synced event in the db.
|
||||||
|
*/
|
||||||
|
internal class LiveTimelineEvent(private val timelineInput: TimelineInput,
|
||||||
|
private val monarchy: Monarchy,
|
||||||
|
private val taskExecutor: TaskExecutor,
|
||||||
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
|
private val roomId: String,
|
||||||
|
private val eventId: String)
|
||||||
|
: TimelineInput.Listener,
|
||||||
|
MediatorLiveData<Optional<TimelineEvent>>() {
|
||||||
|
|
||||||
|
private var queryLiveData: LiveData<Optional<TimelineEvent>>? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
buildAndObserveQuery(eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Makes sure it's made on the main thread
|
||||||
|
private fun buildAndObserveQuery(eventIdToObserve: String) = taskExecutor.executorScope.launch(Dispatchers.Main) {
|
||||||
|
queryLiveData?.also {
|
||||||
|
removeSource(it)
|
||||||
|
}
|
||||||
|
val liveData = monarchy.findAllMappedWithChanges(
|
||||||
|
{ TimelineEventEntity.where(it, roomId = roomId, eventId = eventIdToObserve) },
|
||||||
|
{ timelineEventMapper.map(it) }
|
||||||
|
)
|
||||||
|
queryLiveData = Transformations.map(liveData) { events ->
|
||||||
|
events.firstOrNull().toOptional()
|
||||||
|
}
|
||||||
|
queryLiveData?.also {
|
||||||
|
addSource(it) { newValue -> value = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLocalEchoSynced(roomId: String, localEchoEventId: String, syncedEventId: String) {
|
||||||
|
if (localEchoEventId == eventId) {
|
||||||
|
// rebuild the query with the new eventId
|
||||||
|
buildAndObserveQuery(syncedEventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActive() {
|
||||||
|
super.onActive()
|
||||||
|
// If we are listening to local echo, we want to be aware when event is synced
|
||||||
|
if (LocalEcho.isLocalEchoId(eventId)) {
|
||||||
|
timelineInput.listeners.add(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInactive() {
|
||||||
|
super.onInactive()
|
||||||
|
if (LocalEcho.isLocalEchoId(eventId)) {
|
||||||
|
timelineInput.listeners.remove(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,11 +35,16 @@ internal class TimelineInput @Inject constructor() {
|
||||||
listeners.toSet().forEach { it.onNewTimelineEvents(roomId, eventIds) }
|
listeners.toSet().forEach { it.onNewTimelineEvents(roomId, eventIds) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onLocalEchoSynced(roomId: String, localEchoEventId: String, syncEventId: String) {
|
||||||
|
listeners.toSet().forEach { it.onLocalEchoSynced(roomId, localEchoEventId, syncEventId) }
|
||||||
|
}
|
||||||
|
|
||||||
val listeners = mutableSetOf<Listener>()
|
val listeners = mutableSetOf<Listener>()
|
||||||
|
|
||||||
internal interface Listener {
|
internal interface Listener {
|
||||||
fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent)
|
fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) = Unit
|
||||||
fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState)
|
fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) = Unit
|
||||||
fun onNewTimelineEvents(roomId: String, eventIds: List<String>)
|
fun onNewTimelineEvents(roomId: String, eventIds: List<String>) = Unit
|
||||||
|
fun onLocalEchoSynced(roomId: String, localEchoEventId: String, syncedEventId: String) = Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -400,6 +400,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
||||||
event.mxDecryptionResult = adapter.fromJson(json)
|
event.mxDecryptionResult = adapter.fromJson(json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
timelineInput.onLocalEchoSynced(roomId, it, event.eventId)
|
||||||
// Finally delete the local echo
|
// Finally delete the local echo
|
||||||
sendingEventEntity.deleteOnCascade(true)
|
sendingEventEntity.deleteOnCascade(true)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import com.airbnb.mvrx.MvRxState
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import im.vector.app.core.extensions.canReact
|
import im.vector.app.core.extensions.canReact
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,4 +57,7 @@ data class MessageActionState(
|
||||||
fun senderName(): String = informationData.memberName?.toString() ?: ""
|
fun senderName(): String = informationData.memberName?.toString() ?: ""
|
||||||
|
|
||||||
fun canReact() = timelineEvent()?.canReact() == true && actionPermissions.canReact
|
fun canReact() = timelineEvent()?.canReact() == true && actionPermissions.canReact
|
||||||
|
|
||||||
|
fun sendState(): SendState? = timelineEvent()?.root?.sendState
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
|
import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
|
||||||
import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
|
import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
|
||||||
import im.vector.app.features.home.room.detail.timeline.tools.linkify
|
import im.vector.app.features.home.room.detail.timeline.tools.linkify
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,7 +64,14 @@ class MessageActionsEpoxyController @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send state
|
// Send state
|
||||||
if (state.informationData.sendState.hasFailed()) {
|
val sendState = state.sendState()
|
||||||
|
if (sendState?.isSending().orFalse()) {
|
||||||
|
bottomSheetSendStateItem {
|
||||||
|
id("send_state")
|
||||||
|
showProgress(true)
|
||||||
|
text(stringProvider.getString(R.string.event_status_sending_message))
|
||||||
|
}
|
||||||
|
} else if (sendState?.hasFailed().orFalse()) {
|
||||||
bottomSheetSendStateItem {
|
bottomSheetSendStateItem {
|
||||||
id("send_state")
|
id("send_state")
|
||||||
showProgress(false)
|
showProgress(false)
|
||||||
|
|
|
@ -69,7 +69,6 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
private val vectorPreferences: VectorPreferences
|
private val vectorPreferences: VectorPreferences
|
||||||
) : VectorViewModel<MessageActionState, MessageActionsAction, EmptyViewEvents>(initialState) {
|
) : VectorViewModel<MessageActionState, MessageActionsAction, EmptyViewEvents>(initialState) {
|
||||||
|
|
||||||
private val eventId = initialState.eventId
|
|
||||||
private val informationData = initialState.informationData
|
private val informationData = initialState.informationData
|
||||||
private val room = session.getRoom(initialState.roomId)
|
private val room = session.getRoom(initialState.roomId)
|
||||||
private val pillsPostProcessor by lazy {
|
private val pillsPostProcessor by lazy {
|
||||||
|
@ -91,7 +90,6 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
|
|
||||||
init {
|
init {
|
||||||
observeEvent()
|
observeEvent()
|
||||||
observeReactions()
|
|
||||||
observePowerLevel()
|
observePowerLevel()
|
||||||
observeTimelineEventState()
|
observeTimelineEventState()
|
||||||
}
|
}
|
||||||
|
@ -130,14 +128,14 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
private fun observeEvent() {
|
private fun observeEvent() {
|
||||||
if (room == null) return
|
if (room == null) return
|
||||||
room.rx()
|
room.rx()
|
||||||
.liveTimelineEvent(eventId)
|
.liveTimelineEvent(initialState.eventId)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.execute {
|
.execute {
|
||||||
copy(timelineEvent = it)
|
copy(timelineEvent = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeReactions() {
|
private fun observeReactions(eventId: String) {
|
||||||
if (room == null) return
|
if (room == null) return
|
||||||
room.rx()
|
room.rx()
|
||||||
.liveAnnotationSummary(eventId)
|
.liveAnnotationSummary(eventId)
|
||||||
|
@ -154,8 +152,10 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
private fun observeTimelineEventState() {
|
private fun observeTimelineEventState() {
|
||||||
selectSubscribe(MessageActionState::timelineEvent, MessageActionState::actionPermissions) { timelineEvent, permissions ->
|
selectSubscribe(MessageActionState::timelineEvent, MessageActionState::actionPermissions) { timelineEvent, permissions ->
|
||||||
val nonNullTimelineEvent = timelineEvent() ?: return@selectSubscribe
|
val nonNullTimelineEvent = timelineEvent() ?: return@selectSubscribe
|
||||||
|
observeReactions(nonNullTimelineEvent.eventId)
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
eventId = nonNullTimelineEvent.eventId,
|
||||||
messageBody = computeMessageBody(nonNullTimelineEvent),
|
messageBody = computeMessageBody(nonNullTimelineEvent),
|
||||||
actions = actionsForEvent(nonNullTimelineEvent, permissions)
|
actions = actionsForEvent(nonNullTimelineEvent, permissions)
|
||||||
)
|
)
|
||||||
|
@ -229,6 +229,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
|
private fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
|
||||||
|
val eventId = timelineEvent.eventId
|
||||||
val messageContent = timelineEvent.getLastMessageContent()
|
val messageContent = timelineEvent.getLastMessageContent()
|
||||||
val msgType = messageContent?.msgType
|
val msgType = messageContent?.msgType
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue