diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/LocalEcho.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/LocalEcho.kt new file mode 100644 index 0000000000..ca75871cda --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/LocalEcho.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.matrix.android.api.session.events.model + +import java.util.* + +object LocalEcho { + + private const val PREFIX = "local." + + fun isLocalEchoId(eventId: String) = eventId.startsWith(PREFIX) + + fun createLocalEchoId() = "${PREFIX}${UUID.randomUUID()}" +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadQueries.kt index 9462f582ff..0a925ac1ab 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadQueries.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.database.query import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.events.model.LocalEcho import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ReadReceiptEntity @@ -26,7 +27,9 @@ internal fun isEventRead(monarchy: Monarchy, if (userId.isNullOrBlank() || roomId.isNullOrBlank() || eventId.isNullOrBlank()) { return false } - + if (LocalEcho.isLocalEchoId(eventId)) { + return true + } var isEventRead = false monarchy.doWithRealm { realm -> diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt index d7e2f59742..49474e8e6b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/TimelineEventEntityQueries.kt @@ -61,25 +61,28 @@ internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, roomId: String, includesSending: Boolean, - includedTypes: List = emptyList(), - excludedTypes: List = emptyList()): TimelineEventEntity? { + filterTypes: List = emptyList()): TimelineEventEntity? { val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null - val eventList = if (includesSending && roomEntity.sendingTimelineEvents.isNotEmpty()) { - roomEntity.sendingTimelineEvents + val sendingTimelineEvents = roomEntity.sendingTimelineEvents.where().filterTypes(filterTypes) + val liveEvents = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes) + val query = if (includesSending && sendingTimelineEvents.findAll().isNotEmpty()) { + sendingTimelineEvents } else { - ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents - } - val query = eventList?.where() - if (includedTypes.isNotEmpty()) { - query?.`in`(TimelineEventEntityFields.ROOT.TYPE, includedTypes.toTypedArray()) - } else if (excludedTypes.isNotEmpty()) { - query?.not()?.`in`(TimelineEventEntityFields.ROOT.TYPE, excludedTypes.toTypedArray()) + liveEvents } return query ?.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) ?.findFirst() } +internal fun RealmQuery.filterTypes(filterTypes: List): RealmQuery { + return if (filterTypes.isEmpty()) { + this + } else { + this.`in`(TimelineEventEntityFields.ROOT.TYPE, filterTypes.toTypedArray()) + } +} + internal fun RealmQuery.next(from: Int? = null, strict: Boolean = true): TimelineEventEntity? { if (from != null) { if (strict) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt index 00d6619ce7..224b3bcfeb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt @@ -28,7 +28,6 @@ import im.vector.matrix.android.internal.database.mapper.EventMapper import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.query.create import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.Realm @@ -71,7 +70,7 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor( Timber.w("Event has no room id ${event.eventId}") return@forEach } - val isLocalEcho = LocalEchoEventFactory.isLocalEchoId(event.eventId ?: "") + val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") when (event.type) { EventType.REACTION -> { // we got a reaction!! diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index 0fb3d40bb5..0d28720ec6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -87,7 +87,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId roomSummaryEntity.membership = membership } - val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, includedTypes = PREVIEWABLE_TYPES) + val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = PREVIEWABLE_TYPES) val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain() roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt index c55007c54a..c303e1c215 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.room.prune import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.LocalEcho import im.vector.matrix.android.api.session.events.model.UnsignedData import im.vector.matrix.android.internal.database.helper.updateSenderData import im.vector.matrix.android.internal.database.mapper.ContentMapper @@ -27,7 +28,6 @@ import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.findWithSenderMembershipEvent import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.MoshiProvider -import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.Realm @@ -59,7 +59,7 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M // Check that we know this event EventEntity.where(realm, eventId = redactionEvent.eventId ?: "").findFirst() ?: return - val isLocalEcho = LocalEchoEventFactory.isLocalEchoId(redactionEvent.eventId ?: "") + val isLocalEcho = LocalEcho.isLocalEchoId(redactionEvent.eventId ?: "") Timber.v("Redact event for ${redactionEvent.redacts} localEcho=$isLocalEcho") val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt index 3ed322b483..7e5de176bb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/SetReadMarkersTask.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.session.room.read import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.events.model.LocalEcho import im.vector.matrix.android.internal.database.model.ReadMarkerEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity @@ -26,7 +27,6 @@ import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI -import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.session.sync.ReadReceiptHandler import im.vector.matrix.android.internal.session.sync.RoomFullyReadHandler import im.vector.matrix.android.internal.task.Task @@ -73,7 +73,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI } if (fullyReadEventId != null && isReadMarkerMoreRecent(params.roomId, fullyReadEventId)) { - if (LocalEchoEventFactory.isLocalEchoId(fullyReadEventId)) { + if (LocalEcho.isLocalEchoId(fullyReadEventId)) { Timber.w("Can't set read marker for local event $fullyReadEventId") } else { markers[READ_MARKER] = fullyReadEventId @@ -82,7 +82,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI if (readReceiptEventId != null && !isEventRead(monarchy, userId, params.roomId, readReceiptEventId)) { - if (LocalEchoEventFactory.isLocalEchoId(readReceiptEventId)) { + if (LocalEcho.isLocalEchoId(readReceiptEventId)) { Timber.w("Can't set read receipt for local event $readReceiptEventId") } else { markers[READ_RECEIPT] = readReceiptEventId diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index a4ce62e1f0..49c813ece6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -160,7 +160,7 @@ internal class LocalEchoEventFactory @Inject constructor(@UserId private val use reaction ) ) - val localId = dummyEventId() + val localId = LocalEcho.createLocalEchoId() return Event( roomId = roomId, originServerTs = dummyOriginServerTs(), @@ -264,7 +264,7 @@ internal class LocalEchoEventFactory @Inject constructor(@UserId private val use } private fun createEvent(roomId: String, content: Any? = null): Event { - val localID = dummyEventId() + val localID = LocalEcho.createLocalEchoId() return Event( roomId = roomId, originServerTs = dummyOriginServerTs(), @@ -280,10 +280,6 @@ internal class LocalEchoEventFactory @Inject constructor(@UserId private val use return System.currentTimeMillis() } - private fun dummyEventId(): String { - return "$LOCAL_ID_PREFIX${UUID.randomUUID()}" - } - fun createReplyTextEvent(roomId: String, eventReplied: TimelineEvent, replyText: String, autoMarkdown: Boolean): Event? { // Fallbacks and event representation // TODO Add error/warning logs when any of this is null @@ -383,7 +379,7 @@ internal class LocalEchoEventFactory @Inject constructor(@UserId private val use } */ fun createRedactEvent(roomId: String, eventId: String, reason: String?): Event { - val localID = dummyEventId() + val localID = LocalEcho.createLocalEchoId() return Event( roomId = roomId, originServerTs = dummyOriginServerTs(), @@ -407,8 +403,6 @@ internal class LocalEchoEventFactory @Inject constructor(@UserId private val use } companion object { - const val LOCAL_ID_PREFIX = "local." - // //
// In reply to @@ -419,7 +413,5 @@ internal class LocalEchoEventFactory @Inject constructor(@UserId private val use // // No whitespace because currently breaks temporary formatted text to Span const val REPLY_PATTERN = """
%s%s
%s
%s""" - - fun isLocalEchoId(eventId: String): Boolean = eventId.startsWith(LOCAL_ID_PREFIX) } } diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/TimelineEvent.kt b/vector/src/main/java/im/vector/riotx/core/extensions/TimelineEvent.kt index 6c7a6be1fd..387105c480 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/TimelineEvent.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/TimelineEvent.kt @@ -17,11 +17,12 @@ package im.vector.riotx.core.extensions import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent fun TimelineEvent.canReact(): Boolean { // Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment - return root.getClearType() == EventType.MESSAGE && root.sendState.isSent() && !root.isRedacted() + return root.getClearType() == EventType.MESSAGE && root.sendState == SendState.SYNCED && !root.isRedacted() } fun TimelineEvent.displayReadMarker(myUserId: String): Boolean { diff --git a/vector/src/main/java/im/vector/riotx/core/ui/views/ReadMarkerView.kt b/vector/src/main/java/im/vector/riotx/core/ui/views/ReadMarkerView.kt index ba21d250d8..0fb8b55250 100644 --- a/vector/src/main/java/im/vector/riotx/core/ui/views/ReadMarkerView.kt +++ b/vector/src/main/java/im/vector/riotx/core/ui/views/ReadMarkerView.kt @@ -26,7 +26,7 @@ import android.view.animation.AnimationUtils import im.vector.riotx.R import kotlinx.coroutines.* -private const val DELAY_IN_MS = 1_500L +private const val DELAY_IN_MS = 1_000L class ReadMarkerView @JvmOverloads constructor( context: Context, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 13d9ac4a3d..7c4437d6f0 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -59,6 +59,7 @@ import im.vector.matrix.android.api.permalinks.PermalinkFactory import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.LocalEcho import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.send.SendState @@ -994,10 +995,16 @@ class RoomDetailFragment : return } val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition() - val firstVisibleItem = timelineEventController.adapter.getModelAtPosition(firstVisibleItemPosition) - val nextReadMarkerId = when (firstVisibleItem) { - is BaseEventItem -> firstVisibleItem.getEventIds().firstOrNull() - else -> null + var nextReadMarkerId: String? = null + for (itemPosition in firstVisibleItemPosition until lastVisibleItemPosition) { + val timelineItem = timelineEventController.adapter.getModelAtPosition(itemPosition) + if (timelineItem is BaseEventItem) { + val eventId = timelineItem.getEventIds().firstOrNull() ?: continue + if (!LocalEcho.isLocalEchoId(eventId)) { + nextReadMarkerId = eventId + break + } + } } if (nextReadMarkerId != null) { roomDetailViewModel.process(RoomDetailActions.SetReadMarkerAction(nextReadMarkerId)) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 3b25a9e908..135496264d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -201,7 +201,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted if (canCancel(event)) { add(SimpleAction.Cancel(eventId)) } - } else { + } else if (event.root.sendState == SendState.SYNCED) { if (!event.root.isRedacted()) { if (canReply(event, messageContent)) { add(SimpleAction.Reply(eventId))