mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Add is_falling_back support for rich thread replies
Enhance thread awareness handler so normal replies with thread disabled will be visible in te appropriate thread Fix conflicts
This commit is contained in:
parent
21111922e6
commit
a758ad71e6
13 changed files with 91 additions and 59 deletions
|
@ -389,7 +389,7 @@ fun Event.isReply(): Boolean {
|
|||
}
|
||||
|
||||
fun Event.isReplyRenderedInThread(): Boolean {
|
||||
return isReply() && getRelationContent()?.inReplyTo?.shouldRenderInThread() == true
|
||||
return isReply() && getRelationContent()?.shouldRenderInThread() == true
|
||||
}
|
||||
|
||||
fun Event.isThread(): Boolean = getRelationContentForType(RelationType.THREAD)?.eventId != null
|
||||
|
|
|
@ -26,5 +26,6 @@ data class ReactionInfo(
|
|||
@Json(name = "key") val key: String,
|
||||
// always null for reaction
|
||||
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
|
||||
@Json(name = "option") override val option: Int? = null
|
||||
@Json(name = "option") override val option: Int? = null,
|
||||
@Json(name = "is_falling_back") override val isFallingBack: Boolean? = null
|
||||
) : RelationContent
|
||||
|
|
|
@ -24,4 +24,5 @@ interface RelationContent {
|
|||
val eventId: String?
|
||||
val inReplyTo: ReplyToContent?
|
||||
val option: Int?
|
||||
val isFallingBack: Boolean? // Thread fallback to differentiate replies within threads
|
||||
}
|
||||
|
|
|
@ -23,5 +23,8 @@ data class RelationDefaultContent(
|
|||
@Json(name = "rel_type") override val type: String?,
|
||||
@Json(name = "event_id") override val eventId: String?,
|
||||
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
|
||||
@Json(name = "option") override val option: Int? = null
|
||||
@Json(name = "option") override val option: Int? = null,
|
||||
@Json(name = "is_falling_back") override val isFallingBack: Boolean? = null
|
||||
) : RelationContent
|
||||
|
||||
fun RelationDefaultContent.shouldRenderInThread(): Boolean = isFallingBack == false
|
||||
|
|
|
@ -21,8 +21,5 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ReplyToContent(
|
||||
@Json(name = "event_id") val eventId: String? = null,
|
||||
@Json(name = "render_in") val renderIn: List<String>? = null
|
||||
@Json(name = "event_id") val eventId: String? = null
|
||||
)
|
||||
|
||||
fun ReplyToContent.shouldRenderInThread(): Boolean = renderIn?.contains("m.thread") == true
|
||||
|
|
|
@ -127,7 +127,7 @@ private fun EventEntity.toTimelineEventEntity(roomMemberContentsByUser: HashMap<
|
|||
return timelineEventEntity
|
||||
}
|
||||
|
||||
internal fun ThreadSummaryEntity.Companion.createOrUpdate(
|
||||
internal suspend fun ThreadSummaryEntity.Companion.createOrUpdate(
|
||||
threadSummaryType: ThreadSummaryUpdateType,
|
||||
realm: Realm,
|
||||
roomId: String,
|
||||
|
@ -204,7 +204,7 @@ internal fun ThreadSummaryEntity.Companion.createOrUpdate(
|
|||
}
|
||||
}
|
||||
|
||||
private fun decryptIfNeeded(cryptoService: CryptoService?, eventEntity: EventEntity, roomId: String) {
|
||||
private suspend fun decryptIfNeeded(cryptoService: CryptoService?, eventEntity: EventEntity, roomId: String) {
|
||||
cryptoService ?: return
|
||||
val event = eventEntity.asDomain()
|
||||
if (event.isEncrypted() && event.mxDecryptionResult == null && event.eventId != null) {
|
||||
|
|
|
@ -172,7 +172,7 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||
replyText = replyInThreadText,
|
||||
autoMarkdown = autoMarkdown,
|
||||
rootThreadEventId = rootThreadEventId,
|
||||
showInThread = true
|
||||
showInThread = false
|
||||
)
|
||||
?.also {
|
||||
saveLocalEcho(it)
|
||||
|
|
|
@ -100,7 +100,7 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor:
|
|||
eventReplied = originalTimelineEvent,
|
||||
replyText = newBodyText,
|
||||
autoMarkdown = false,
|
||||
showInThread = false
|
||||
showInThread = false // Test that value
|
||||
)?.copy(
|
||||
eventId = replyToEdit.eventId
|
||||
) ?: return NoOpCancellable
|
||||
|
|
|
@ -560,7 +560,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
relatesTo = generateReplyRelationContent(
|
||||
eventId = eventId,
|
||||
rootThreadEventId = rootThreadEventId,
|
||||
showAsReply = showInThread))
|
||||
showInThread = showInThread))
|
||||
return createMessageEvent(roomId, content)
|
||||
}
|
||||
|
||||
|
@ -570,18 +570,20 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
* "m.relates_to": {
|
||||
* "rel_type": "m.thread",
|
||||
* "event_id": "$thread_root",
|
||||
* "is_falling_back": false,
|
||||
* "m.in_reply_to": {
|
||||
* "event_id": "$event_target",
|
||||
* "render_in": ["m.thread"]
|
||||
* }
|
||||
* }
|
||||
* "event_id": "$event_target"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
private fun generateReplyRelationContent(eventId: String, rootThreadEventId: String? = null, showAsReply: Boolean): RelationDefaultContent =
|
||||
private fun generateReplyRelationContent(eventId: String, rootThreadEventId: String? = null, showInThread: Boolean): RelationDefaultContent =
|
||||
rootThreadEventId?.let {
|
||||
RelationDefaultContent(
|
||||
type = RelationType.THREAD,
|
||||
eventId = it,
|
||||
inReplyTo = ReplyToContent(eventId = eventId, renderIn = if (showAsReply) arrayListOf("m.thread") else null))
|
||||
isFallingBack = showInThread,
|
||||
// False when is a rich reply from within a thread, and true when is a reply that should be visible from threads
|
||||
inReplyTo = ReplyToContent(eventId = eventId))
|
||||
} ?: RelationDefaultContent(null, null, ReplyToContent(eventId = eventId))
|
||||
|
||||
private fun buildFormattedReply(permalink: String, userLink: String, userId: String, bodyFormatted: String, newBodyFormatted: String): String {
|
||||
|
|
|
@ -60,6 +60,7 @@ fun TextContent.toThreadTextContent(
|
|||
relatesTo = RelationDefaultContent(
|
||||
type = RelationType.THREAD,
|
||||
eventId = rootThreadEventId,
|
||||
isFallingBack = true,
|
||||
inReplyTo = ReplyToContent(
|
||||
eventId = latestThreadEventId
|
||||
)),
|
||||
|
|
|
@ -102,11 +102,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
data class LEFT(val data: Map<String, RoomSync>) : HandlingStrategy()
|
||||
}
|
||||
|
||||
fun handle(realm: Realm,
|
||||
roomsSyncResponse: RoomsSyncResponse,
|
||||
isInitialSync: Boolean,
|
||||
aggregator: SyncResponsePostTreatmentAggregator,
|
||||
reporter: ProgressReporter? = null) {
|
||||
suspend fun handle(realm: Realm,
|
||||
roomsSyncResponse: RoomsSyncResponse,
|
||||
isInitialSync: Boolean,
|
||||
aggregator: SyncResponsePostTreatmentAggregator,
|
||||
reporter: ProgressReporter? = null) {
|
||||
Timber.v("Execute transaction from $this")
|
||||
handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, aggregator, reporter)
|
||||
handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, aggregator, reporter)
|
||||
|
@ -121,11 +121,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
}
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun handleRoomSync(realm: Realm,
|
||||
handlingStrategy: HandlingStrategy,
|
||||
isInitialSync: Boolean,
|
||||
aggregator: SyncResponsePostTreatmentAggregator,
|
||||
reporter: ProgressReporter?) {
|
||||
private suspend fun handleRoomSync(realm: Realm,
|
||||
handlingStrategy: HandlingStrategy,
|
||||
isInitialSync: Boolean,
|
||||
aggregator: SyncResponsePostTreatmentAggregator,
|
||||
reporter: ProgressReporter?) {
|
||||
val insertType = if (isInitialSync) {
|
||||
EventInsertType.INITIAL_SYNC
|
||||
} else {
|
||||
|
@ -158,11 +158,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
realm.insertOrUpdate(rooms)
|
||||
}
|
||||
|
||||
private fun insertJoinRoomsFromInitSync(realm: Realm,
|
||||
handlingStrategy: HandlingStrategy.JOINED,
|
||||
syncLocalTimeStampMillis: Long,
|
||||
aggregator: SyncResponsePostTreatmentAggregator,
|
||||
reporter: ProgressReporter?) {
|
||||
private suspend fun insertJoinRoomsFromInitSync(realm: Realm,
|
||||
handlingStrategy: HandlingStrategy.JOINED,
|
||||
syncLocalTimeStampMillis: Long,
|
||||
aggregator: SyncResponsePostTreatmentAggregator,
|
||||
reporter: ProgressReporter?) {
|
||||
val bestChunkSize = computeBestChunkSize(
|
||||
listSize = handlingStrategy.data.keys.size,
|
||||
limit = (initialSyncStrategy as? InitialSyncStrategy.Optimized)?.maxRoomsToInsert ?: Int.MAX_VALUE
|
||||
|
@ -200,12 +200,12 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleJoinedRoom(realm: Realm,
|
||||
roomId: String,
|
||||
roomSync: RoomSync,
|
||||
insertType: EventInsertType,
|
||||
syncLocalTimestampMillis: Long,
|
||||
aggregator: SyncResponsePostTreatmentAggregator): RoomEntity {
|
||||
private suspend fun handleJoinedRoom(realm: Realm,
|
||||
roomId: String,
|
||||
roomSync: RoomSync,
|
||||
insertType: EventInsertType,
|
||||
syncLocalTimestampMillis: Long,
|
||||
aggregator: SyncResponsePostTreatmentAggregator): RoomEntity {
|
||||
Timber.v("Handle join sync for room $roomId")
|
||||
|
||||
val ephemeralResult = (roomSync.ephemeral as? LazyRoomSyncEphemeral.Parsed)
|
||||
|
@ -351,15 +351,15 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
|||
return roomEntity
|
||||
}
|
||||
|
||||
private fun handleTimelineEvents(realm: Realm,
|
||||
roomId: String,
|
||||
roomEntity: RoomEntity,
|
||||
eventList: List<Event>,
|
||||
prevToken: String? = null,
|
||||
isLimited: Boolean = true,
|
||||
insertType: EventInsertType,
|
||||
syncLocalTimestampMillis: Long,
|
||||
aggregator: SyncResponsePostTreatmentAggregator): ChunkEntity {
|
||||
private suspend fun handleTimelineEvents(realm: Realm,
|
||||
roomId: String,
|
||||
roomEntity: RoomEntity,
|
||||
eventList: List<Event>,
|
||||
prevToken: String? = null,
|
||||
isLimited: Boolean = true,
|
||||
insertType: EventInsertType,
|
||||
syncLocalTimestampMillis: Long,
|
||||
aggregator: SyncResponsePostTreatmentAggregator): ChunkEntity {
|
||||
val lastChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomEntity.roomId)
|
||||
if (isLimited && lastChunk != null) {
|
||||
lastChunk.deleteOnCascade(deleteStateEvents = false, canDeleteRoot = true)
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
|
|||
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
|
@ -170,8 +171,9 @@ internal class ThreadsAwarenessHandler @Inject constructor(
|
|||
event.mxDecryptionResult?.payload?.toMutableMap() ?: return null
|
||||
}
|
||||
val eventBody = event.getDecryptedTextSummary() ?: return null
|
||||
val threadRelation = getRootThreadRelationContent(event)
|
||||
val eventIdToInject = getPreviousEventOrRoot(event) ?: run {
|
||||
return@makeEventThreadAware injectFallbackIndicator(event, eventBody, eventEntity, eventPayload)
|
||||
return@makeEventThreadAware injectFallbackIndicator(event, eventBody, eventEntity, eventPayload, threadRelation)
|
||||
}
|
||||
val eventToInject = getEventFromDB(realm, eventIdToInject)
|
||||
val eventToInjectBody = eventToInject?.getDecryptedTextSummary()
|
||||
|
@ -183,17 +185,19 @@ internal class ThreadsAwarenessHandler @Inject constructor(
|
|||
roomId = roomId,
|
||||
eventBody = eventBody,
|
||||
eventToInject = eventToInject,
|
||||
eventToInjectBody = eventToInjectBody) ?: return null
|
||||
eventToInjectBody = eventToInjectBody,
|
||||
threadRelation = threadRelation) ?: return null
|
||||
|
||||
// update the event
|
||||
contentForNonEncrypted = updateEventEntity(event, eventEntity, eventPayload, messageTextContent)
|
||||
} else {
|
||||
contentForNonEncrypted = injectFallbackIndicator(event, eventBody, eventEntity, eventPayload)
|
||||
contentForNonEncrypted = injectFallbackIndicator(event, eventBody, eventEntity, eventPayload, threadRelation)
|
||||
}
|
||||
|
||||
// Now lets try to find relations for improved results, while some events may come with reverse order
|
||||
eventEntity?.let {
|
||||
// When eventEntity is not null means that we are not from within roomSyncHandler
|
||||
handleEventsThatRelatesTo(realm, roomId, event, eventBody, false)
|
||||
handleEventsThatRelatesTo(realm, roomId, event, eventBody, false, threadRelation)
|
||||
}
|
||||
return contentForNonEncrypted
|
||||
}
|
||||
|
@ -205,11 +209,16 @@ internal class ThreadsAwarenessHandler @Inject constructor(
|
|||
* @param event the current event received
|
||||
* @return The content to inject in the roomSyncHandler live events
|
||||
*/
|
||||
private fun handleRootThreadEventsIfNeeded(realm: Realm, roomId: String, eventEntity: EventEntity?, event: Event): String? {
|
||||
private fun handleRootThreadEventsIfNeeded(
|
||||
realm: Realm,
|
||||
roomId: String,
|
||||
eventEntity: EventEntity?,
|
||||
event: Event
|
||||
): String? {
|
||||
if (!isThreadEvent(event) && cacheEventRootId.contains(eventEntity?.eventId)) {
|
||||
eventEntity?.let {
|
||||
val eventBody = event.getDecryptedTextSummary() ?: return null
|
||||
return handleEventsThatRelatesTo(realm, roomId, event, eventBody, true)
|
||||
return handleEventsThatRelatesTo(realm, roomId, event, eventBody, true, null)
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
@ -224,7 +233,14 @@ internal class ThreadsAwarenessHandler @Inject constructor(
|
|||
* @param isFromCache determines whether or not we already know this is root thread event
|
||||
* @return The content to inject in the roomSyncHandler live events
|
||||
*/
|
||||
private fun handleEventsThatRelatesTo(realm: Realm, roomId: String, event: Event, eventBody: String, isFromCache: Boolean): String? {
|
||||
private fun handleEventsThatRelatesTo(
|
||||
realm: Realm,
|
||||
roomId: String,
|
||||
event: Event,
|
||||
eventBody: String,
|
||||
isFromCache: Boolean,
|
||||
threadRelation: RelationDefaultContent?
|
||||
): String? {
|
||||
event.eventId ?: return null
|
||||
val rootThreadEventId = if (isFromCache) event.eventId else event.getRootThreadEventId() ?: return null
|
||||
eventThatRelatesTo(realm, event.eventId, rootThreadEventId)?.forEach { eventEntityFound ->
|
||||
|
@ -236,7 +252,8 @@ internal class ThreadsAwarenessHandler @Inject constructor(
|
|||
roomId = roomId,
|
||||
eventBody = newEventBody,
|
||||
eventToInject = event,
|
||||
eventToInjectBody = eventBody) ?: return null
|
||||
eventToInjectBody = eventBody,
|
||||
threadRelation = threadRelation) ?: return null
|
||||
|
||||
return updateEventEntity(newEventFound, eventEntityFound, newEventPayload, messageTextContent)
|
||||
}
|
||||
|
@ -280,7 +297,9 @@ internal class ThreadsAwarenessHandler @Inject constructor(
|
|||
private fun injectEvent(roomId: String,
|
||||
eventBody: String,
|
||||
eventToInject: Event,
|
||||
eventToInjectBody: String): Content? {
|
||||
eventToInjectBody: String,
|
||||
threadRelation: RelationDefaultContent?
|
||||
): Content? {
|
||||
val eventToInjectId = eventToInject.eventId ?: return null
|
||||
val eventIdToInjectSenderId = eventToInject.senderId.orEmpty()
|
||||
val permalink = permalinkFactory.createPermalink(roomId, eventToInjectId, false)
|
||||
|
@ -293,6 +312,7 @@ internal class ThreadsAwarenessHandler @Inject constructor(
|
|||
eventBody)
|
||||
|
||||
return MessageTextContent(
|
||||
relatesTo = threadRelation,
|
||||
msgType = MessageType.MSGTYPE_TEXT,
|
||||
format = MessageFormat.FORMAT_MATRIX_HTML,
|
||||
body = eventBody,
|
||||
|
@ -306,12 +326,14 @@ internal class ThreadsAwarenessHandler @Inject constructor(
|
|||
private fun injectFallbackIndicator(event: Event,
|
||||
eventBody: String,
|
||||
eventEntity: EventEntity?,
|
||||
eventPayload: MutableMap<String, Any>): String? {
|
||||
eventPayload: MutableMap<String, Any>,
|
||||
threadRelation: RelationDefaultContent?): String? {
|
||||
val replyFormatted = LocalEchoEventFactory.QUOTE_PATTERN.format(
|
||||
"In reply to a thread",
|
||||
eventBody)
|
||||
|
||||
val messageTextContent = MessageTextContent(
|
||||
relatesTo = threadRelation,
|
||||
msgType = MessageType.MSGTYPE_TEXT,
|
||||
format = MessageFormat.FORMAT_MATRIX_HTML,
|
||||
body = eventBody,
|
||||
|
@ -359,6 +381,9 @@ internal class ThreadsAwarenessHandler @Inject constructor(
|
|||
private fun getRootThreadEventId(event: Event): String? =
|
||||
event.content.toModel<MessageRelationContent>()?.relatesTo?.eventId
|
||||
|
||||
private fun getRootThreadRelationContent(event: Event): RelationDefaultContent? =
|
||||
event.content.toModel<MessageRelationContent>()?.relatesTo
|
||||
|
||||
private fun getPreviousEventOrRoot(event: Event): String? =
|
||||
event.content.toModel<MessageRelationContent>()?.relatesTo?.inReplyTo?.eventId
|
||||
|
||||
|
|
|
@ -465,7 +465,8 @@ class MessageComposerViewModel @AssistedInject constructor(
|
|||
// is original event a reply?
|
||||
val relationContent = state.sendMode.timelineEvent.getRelationContent()
|
||||
val inReplyTo = if (state.rootThreadEventId != null) {
|
||||
if (relationContent?.inReplyTo?.shouldRenderInThread() == true) {
|
||||
// Thread event
|
||||
if (relationContent?.shouldRenderInThread() == true) {
|
||||
// Reply within a thread event
|
||||
relationContent.inReplyTo?.eventId
|
||||
} else {
|
||||
|
@ -509,6 +510,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
|||
is SendMode.Reply -> {
|
||||
val timelineEvent = state.sendMode.timelineEvent
|
||||
val showInThread = state.sendMode.timelineEvent.root.isThread() && state.rootThreadEventId == null
|
||||
// If threads are disabled this will make the fallback replies visible to clients with threads enabled
|
||||
val rootThreadEventId = if (showInThread) timelineEvent.root.getRootThreadEventId() else null
|
||||
state.rootThreadEventId?.let {
|
||||
room.replyInThread(
|
||||
|
|
Loading…
Reference in a new issue