From 24e119c150f305ab78823e138e250c5708e1e449 Mon Sep 17 00:00:00 2001 From: N-Pex Date: Fri, 25 Oct 2024 11:48:54 +0200 Subject: [PATCH] Improved read receipt handling Also, fix audio auto play of consecutive messages, even if they are in threads. --- src/components/AudioLayout.vue | 2 +- src/components/Chat.vue | 128 +++++++++++++++------------------ 2 files changed, 60 insertions(+), 70 deletions(-) diff --git a/src/components/AudioLayout.vue b/src/components/AudioLayout.vue index d989815..fa920aa 100644 --- a/src/components/AudioLayout.vue +++ b/src/components/AudioLayout.vue @@ -258,7 +258,7 @@ export default { } this.updateVisualization(); if (this.currentAudioEvent) { - this.$emit("mark-read", this.currentAudioEvent.getId(), this.currentAudioEvent.getId()); + this.$emit("mark-read", [this.currentAudioEvent]); } }, audioPlaybackPaused() { diff --git a/src/components/Chat.vue b/src/components/Chat.vue index a283675..5efd010 100644 --- a/src/components/Chat.vue +++ b/src/components/Chat.vue @@ -1941,80 +1941,62 @@ export default { if (this.$matrix.currentRoomBeingPurged) { return; } - - let eventIdFirst = null; - let eventIdLast = null; if (!this.useVoiceMode && !this.useFileModeNonAdmin) { - const container = this.chatContainer; - const elFirst = util.getFirstVisibleElement(container, (item) => item.hasAttribute("eventId")); - const elLast = util.getLastVisibleElement(container, (item) => item.hasAttribute("eventId")); - if (elFirst && elLast) { - if (this.reverseOrder) { - // For reverse order, the "first visible" is actually later in time, so swap them - eventIdFirst = elLast.getAttribute("eventId"); - eventIdLast = elFirst.getAttribute("eventId"); - } else { - eventIdFirst = elFirst.getAttribute("eventId"); - eventIdLast = elLast.getAttribute("eventId"); - } - } - } - if (eventIdFirst && eventIdLast) { - this.rrTimer = setTimeout(() => { this.rrTimerElapsed(eventIdFirst, eventIdLast) }, READ_RECEIPT_TIMEOUT); + this.rrTimer = setTimeout(() => { this.rrTimerElapsed() }, READ_RECEIPT_TIMEOUT); } }, - rrTimerElapsed(eventIdFirst, eventIdLast) { + rrTimerElapsed() { this.rrTimer = null; - this.sendRR(eventIdFirst, eventIdLast); + this.sendRR(); this.restartRRTimer(); }, - sendRR(eventIdFirst, eventIdLast) { - console.log("SEND RR", eventIdFirst, eventIdLast); - if (eventIdLast && this.room) { - var event = this.room.findEventById(eventIdLast); - const index = this.events.indexOf(event); + sendRR(onlyTheseEvents) { + let lastTimestamp = 0; + if (this.lastRR) { + lastTimestamp = this.lastRR.getTs(); + } - // Walk backwards through visible events to the first one that is incoming - // - var lastTimestamp = 0; - if (this.lastRR) { - lastTimestamp = this.lastRR.getTs(); - } + let eventsToConsider = onlyTheseEvents; + if (!eventsToConsider) { + // If events to consider is not given (used by Audio layout) then consider all visible events (with some restrictions). + let visibleEventIds = util.findVisibleElements(this.chatContainer).filter(el => el.hasAttribute("eventId")).map(el => el.getAttribute("eventId")); - for (var i = index; i >= 0; i--) { - event = this.events[i]; - if (event == this.lastRR || event.getTs() <= lastTimestamp) { - // Already sent this or too old... - break; - } - // Make sure it's not a local echo event... - if (!event.getId().startsWith("~")) { - // Send read receipt - this.$matrix.matrixClient - .sendReadReceipt(event) - .then(() => { - this.$matrix.matrixClient.setRoomReadMarkers(this.room.roomId, event.getId()); - }) - .then(() => { - console.log("RR sent for event: " + event.getId()); - this.lastRR = event; - }) - .catch((err) => { - console.log("Failed to update read marker: ", err); - }) - .finally(() => { - this.restartRRTimer(); - }); - return; // Bail out here + eventsToConsider = this.events.filter(e => { + if (e.getTs() > lastTimestamp && + // Make sure it's not a local echo event... + !e.getId().startsWith("~")) { + if (e.isRelation()) { + return visibleEventIds.includes(e.getAssociatedId()); + } else if (e.getType() == "m.room.pinned_events") { + // Ignore the pin event. If we pin an older message it does not automatically mean we have read the + // messages between. + return false; + } else { + return visibleEventIds.includes(e.getId()); + } } + return false; + }); + } - // Stop iterating at first visible - if (event.getId() == eventIdFirst) { - break; - } - } + eventsToConsider.sort((a, b) => b.getTs() - a.getTs()); + if (eventsToConsider.length > 0 && eventsToConsider[0] != this.lastRR) { + const eventToSendRRFor = eventsToConsider[0]; + + // Send read receipt + this.$matrix.matrixClient.setRoomReadMarkers(this.room.roomId, eventToSendRRFor.getId(), eventToSendRRFor) + .then(() => { + console.log("RR sent for event", eventToSendRRFor.getId(), eventToSendRRFor.getType()); + this.lastRR = eventToSendRRFor; + }) + .catch((err) => { + console.log("Failed to update read marker: ", err); + }) + .finally(() => { + this.restartRRTimer(); + }); } }, @@ -2109,13 +2091,21 @@ export default { audioPlaybackEnded(matrixEventId) { if (!this.useVoiceMode) { // Voice mode has own autoplay handling inside "AudioLayout"! // Auto play consecutive audio messages, either incoming or sent. - const filteredEvents = this.filteredEvents; - const event = filteredEvents.find(e => e.getId() === matrixEventId); - if (event && event.nextDisplayedEvent) { - const nextEvent = event.nextDisplayedEvent; - if (nextEvent.getContent().msgtype === "m.audio") { - // Yes, audio event! - this.$audioPlayer.play(nextEvent, this.timelineSet); + let event = this.events.find(e => e.getId() === matrixEventId); + if (event) { + event = event.parentThread ? event.parentThread : event; + if (event.nextDisplayedEvent) { + const nextEvent = event.nextDisplayedEvent; + if (nextEvent.getContent().msgtype === "m.audio") { + // Yes, audio event! + this.$audioPlayer.play(nextEvent, this.timelineSet); + } else if (nextEvent.isMxThread) { + const children = this.timelineSet.relations.getAllChildEventsForEvent(nextEvent.getId()); + if (children && children.length > 0 && children[0].getContent().msgtype === "m.audio") { + // Yes, audio event! + this.$audioPlayer.play(children[0], this.timelineSet); + } + } } } }