Fix some issues and make test passes

This commit is contained in:
Ganard 2020-02-03 18:45:50 +01:00
parent f454078c6b
commit 1728d31401
9 changed files with 112 additions and 118 deletions

View file

@ -241,6 +241,11 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
val bobEventsListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) {
// noop
}
override fun onNewTimelineEvents(eventIds: List<String>) {
// noop
}
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {

View file

@ -20,15 +20,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.database.helper.add
import im.vector.matrix.android.internal.database.helper.lastStateIndex
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.database.helper.addTimelineEvent
import im.vector.matrix.android.internal.database.helper.merge
import im.vector.matrix.android.internal.database.mapper.toEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.SessionRealmModule
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeMessageEvent
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeRoomMemberEvent
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.kotlin.createObject
@ -58,8 +58,11 @@ internal class ChunkEntityTest : InstrumentedTest {
fun add_shouldAdd_whenNotAlreadyIncluded() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeMessageEvent()
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED).let {
realm.copyToRealmOrUpdate(it)
}
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.timelineEvents.size shouldEqual 1
}
}
@ -68,65 +71,23 @@ internal class ChunkEntityTest : InstrumentedTest {
fun add_shouldNotAdd_whenAlreadyIncluded() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeMessageEvent()
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED).let {
realm.copyToRealmOrUpdate(it)
}
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.addTimelineEvent(ROOM_ID, fakeEvent, PaginationDirection.FORWARDS, emptyMap())
chunk.timelineEvents.size shouldEqual 1
}
}
@Test
fun add_shouldStateIndexIncremented_whenStateEventIsAddedForward() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeRoomMemberEvent()
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 1
}
}
@Test
fun add_shouldStateIndexNotIncremented_whenNoStateEventIsAdded() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeMessageEvent()
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 0
}
}
@Test
fun addAll_shouldStateIndexIncremented_whenStateEventsAreAddedForward() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvents = createFakeListOfEvents(30)
val numberOfStateEvents = fakeEvents.filter { it.isStateEvent() }.size
chunk.addAll("roomId", fakeEvents, PaginationDirection.FORWARDS)
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual numberOfStateEvents
}
}
@Test
fun addAll_shouldStateIndexDecremented_whenStateEventsAreAddedBackward() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvents = createFakeListOfEvents(30)
val numberOfStateEvents = fakeEvents.filter { it.isStateEvent() }.size
val lastIsState = fakeEvents.last().isStateEvent()
val expectedStateIndex = if (lastIsState) -numberOfStateEvents + 1 else -numberOfStateEvents
chunk.addAll("roomId", fakeEvents, PaginationDirection.BACKWARDS)
chunk.lastStateIndex(PaginationDirection.BACKWARDS) shouldEqual expectedStateIndex
}
}
@Test
fun merge_shouldAddEvents_whenMergingBackward() {
monarchy.runTransactionSync { realm ->
val chunk1: ChunkEntity = realm.createObject()
val chunk2: ChunkEntity = realm.createObject()
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
chunk1.timelineEvents.size shouldEqual 60
}
}
@ -140,9 +101,9 @@ internal class ChunkEntityTest : InstrumentedTest {
val eventsForChunk2 = eventsForChunk1 + createFakeListOfEvents(10)
chunk1.isLastForward = true
chunk2.isLastForward = false
chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS)
chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS)
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.addAll(ROOM_ID, eventsForChunk1, PaginationDirection.FORWARDS)
chunk2.addAll(ROOM_ID, eventsForChunk2, PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
chunk1.timelineEvents.size shouldEqual 40
chunk1.isLastForward.shouldBeTrue()
}
@ -155,9 +116,9 @@ internal class ChunkEntityTest : InstrumentedTest {
val chunk2: ChunkEntity = realm.createObject()
val prevToken = "prev_token"
chunk1.prevToken = prevToken
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge("roomId", chunk2, PaginationDirection.FORWARDS)
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.FORWARDS)
chunk1.prevToken shouldEqual prevToken
}
}
@ -169,19 +130,25 @@ internal class ChunkEntityTest : InstrumentedTest {
val chunk2: ChunkEntity = realm.createObject()
val nextToken = "next_token"
chunk1.nextToken = nextToken
chunk1.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll("roomId", createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
chunk1.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(ROOM_ID, createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(ROOM_ID, chunk2, PaginationDirection.BACKWARDS)
chunk1.nextToken shouldEqual nextToken
}
}
private fun ChunkEntity.addAll(roomId: String,
events: List<Event>,
direction: PaginationDirection,
stateIndexOffset: Int = 0) {
direction: PaginationDirection) {
events.forEach { event ->
add(roomId, event, direction)
val fakeEvent = event.toEntity(roomId, SendState.SYNCED).let {
realm.copyToRealmOrUpdate(it)
}
addTimelineEvent(roomId, fakeEvent, direction, emptyMap())
}
}
companion object {
private const val ROOM_ID = "roomId"
}
}

View file

@ -62,27 +62,7 @@ internal fun ChunkEntity.merge(roomId: String, chunkToMerge: ChunkEntity, direct
}
return eventsToMerge
.forEach {
if (timelineEvents.find(it.eventId) == null) {
val eventId = it.eventId
if (timelineEvents.find(eventId) != null) {
return
}
val displayIndex = nextDisplayIndex(direction)
val localId = TimelineEventEntity.nextId(realm)
val copied = localRealm.createObject<TimelineEventEntity>().apply {
this.localId = localId
this.root = it.root
this.eventId = it.eventId
this.roomId = it.roomId
this.annotations = it.annotations
this.readReceipts = it.readReceipts
this.displayIndex = displayIndex
this.senderAvatar = it.senderAvatar
this.senderName = it.senderName
this.isUniqueDisplayName = it.isUniqueDisplayName
}
timelineEvents.add(copied)
}
addTimelineEventFromMerge(localRealm, it, direction)
}
}
@ -108,7 +88,7 @@ internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity,
internal fun ChunkEntity.addTimelineEvent(roomId: String,
eventEntity: EventEntity,
direction: PaginationDirection,
roomMemberContentsByUser: HashMap<String, RoomMemberContent?>) {
roomMemberContentsByUser: Map<String, RoomMemberContent?>) {
val eventId = eventEntity.eventId
if (timelineEvents.find(eventId) != null) {
return
@ -130,28 +110,60 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String,
val roomMemberContent = roomMemberContentsByUser[senderId]
this.senderAvatar = roomMemberContent?.avatarUrl
this.senderName = roomMemberContent?.displayName
if (roomMemberContent?.displayName != null) {
val isHistoricalUnique = roomMemberContentsByUser.values.find {
it != roomMemberContent && it?.displayName == roomMemberContent.displayName
} == null
isUniqueDisplayName = if (isLastForward) {
val isLiveUnique = RoomMemberSummaryEntity
.where(realm, roomId)
.equalTo(RoomMemberSummaryEntityFields.DISPLAY_NAME, roomMemberContent.displayName)
.findAll().none {
!roomMemberContentsByUser.containsKey(it.userId)
}
isHistoricalUnique && isLiveUnique
} else {
isHistoricalUnique
}
isUniqueDisplayName = if (roomMemberContent?.displayName != null) {
computeIsUnique(realm, roomId, isLastForward, roomMemberContent, roomMemberContentsByUser)
} else {
isUniqueDisplayName = true
true
}
}
timelineEvents.add(timelineEventEntity)
}
private fun computeIsUnique(
realm: Realm,
roomId: String,
isLastForward: Boolean,
myRoomMemberContent: RoomMemberContent,
roomMemberContentsByUser: Map<String, RoomMemberContent?>
): Boolean {
val isHistoricalUnique = roomMemberContentsByUser.values.find {
it != myRoomMemberContent && it?.displayName == myRoomMemberContent.displayName
} == null
return if (isLastForward) {
val isLiveUnique = RoomMemberSummaryEntity
.where(realm, roomId)
.equalTo(RoomMemberSummaryEntityFields.DISPLAY_NAME, myRoomMemberContent.displayName)
.findAll().none {
!roomMemberContentsByUser.containsKey(it.userId)
}
isHistoricalUnique && isLiveUnique
} else {
isHistoricalUnique
}
}
private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEntity: TimelineEventEntity, direction: PaginationDirection) {
val eventId = timelineEventEntity.eventId
if (timelineEvents.find(eventId) != null) {
return
}
val displayIndex = nextDisplayIndex(direction)
val localId = TimelineEventEntity.nextId(realm)
val copied = realm.createObject<TimelineEventEntity>().apply {
this.localId = localId
this.root = timelineEventEntity.root
this.eventId = timelineEventEntity.eventId
this.roomId = timelineEventEntity.roomId
this.annotations = timelineEventEntity.annotations
this.readReceipts = timelineEventEntity.readReceipts
this.displayIndex = displayIndex
this.senderAvatar = timelineEventEntity.senderAvatar
this.senderName = timelineEventEntity.senderName
this.isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName
}
timelineEvents.add(copied)
}
private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventEntity, senderId: String): ReadReceiptsSummaryEntity {
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventEntity.eventId).findFirst()
?: realm.createObject<ReadReceiptsSummaryEntity>(eventEntity.eventId).apply {

View file

@ -20,7 +20,6 @@ import androidx.annotation.WorkerThread
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.homeserver.HomeServerPinger
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.runBlocking
import java.util.Collections
import java.util.concurrent.atomic.AtomicBoolean

View file

@ -120,7 +120,7 @@ internal class DefaultTimeline(
if (!results.isLoaded || !results.isValid) {
return@OrderedRealmCollectionChangeListener
}
handleUpdates(changeSet)
handleUpdates(results, changeSet)
}
private val relationsListener = OrderedRealmCollectionChangeListener<RealmResults<EventAnnotationsSummaryEntity>> { collection, changeSet ->
@ -185,7 +185,7 @@ internal class DefaultTimeline(
.filterEventsWithSettings()
.findAll()
handleInitialLoad()
filteredEvents.addChangeListener(eventsChangeListener)
nonFilteredEvents.addChangeListener(eventsChangeListener)
eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId)
.findAllAsync()
@ -215,8 +215,8 @@ internal class DefaultTimeline(
if (this::eventRelations.isInitialized) {
eventRelations.removeAllChangeListeners()
}
if (this::filteredEvents.isInitialized) {
filteredEvents.removeAllChangeListeners()
if (this::nonFilteredEvents.isInitialized) {
nonFilteredEvents.removeAllChangeListeners()
}
if (settings.shouldHandleHiddenReadReceipts()) {
hiddenReadReceipts.dispose()
@ -452,7 +452,7 @@ internal class DefaultTimeline(
var shouldFetchInitialEvent = false
val currentInitialEventId = initialEventId
val initialDisplayIndex = if (currentInitialEventId == null) {
filteredEvents.firstOrNull()?.displayIndex
nonFilteredEvents.firstOrNull()?.displayIndex
} else {
val initialEvent = nonFilteredEvents.where()
.equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId)
@ -480,7 +480,7 @@ internal class DefaultTimeline(
/**
* This has to be called on TimelineThread as it access realm live results
*/
private fun handleUpdates(changeSet: OrderedCollectionChangeSet) {
private fun handleUpdates(results: RealmResults<TimelineEventEntity>, changeSet: OrderedCollectionChangeSet) {
// If changeSet has deletion we are having a gap, so we clear everything
if (changeSet.deletionRanges.isNotEmpty()) {
clearAllValues()
@ -488,9 +488,9 @@ internal class DefaultTimeline(
var postSnapshot = false
changeSet.insertionRanges.forEach { range ->
val (startDisplayIndex, direction) = if (range.startIndex == 0) {
Pair(filteredEvents[range.length - 1]!!.displayIndex, Timeline.Direction.FORWARDS)
Pair(results[range.length - 1]!!.displayIndex, Timeline.Direction.FORWARDS)
} else {
Pair(filteredEvents[range.startIndex]!!.displayIndex, Timeline.Direction.BACKWARDS)
Pair(results[range.startIndex]!!.displayIndex, Timeline.Direction.BACKWARDS)
}
val state = getState(direction)
if (state.isPaginating) {
@ -503,7 +503,7 @@ internal class DefaultTimeline(
}
}
changeSet.changes.forEach { index ->
val eventEntity = filteredEvents[index]
val eventEntity = results[index]
eventEntity?.eventId?.let { eventId ->
postSnapshot = rebuildEvent(eventId) {
buildTimelineEvent(eventEntity)

View file

@ -167,7 +167,6 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
private fun handleReachEnd(realm: Realm, roomId: String, direction: PaginationDirection, currentChunk: ChunkEntity) {
Timber.v("Reach end of $roomId")
roomId.isBlank()
if (direction == PaginationDirection.FORWARDS) {
val currentLiveChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
if (currentChunk != currentLiveChunk) {

View file

@ -395,4 +395,4 @@ class EllipsizingTextView @JvmOverloads constructor(context: Context, attrs: Att
val ellipsizeColor = Color.argb(ELLIPSIZE_ALPHA, Color.red(currentTextColor), Color.green(currentTextColor), Color.blue(currentTextColor))
ELLIPSIS.setSpan(ForegroundColorSpan(ellipsizeColor), 0, ELLIPSIS.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
}

View file

@ -350,9 +350,11 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
jumpToBottomView.visibility = View.INVISIBLE
if (!roomDetailViewModel.timeline.isLive) {
scrollOnNewMessageCallback.forceScrollOnNextUpdate()
roomDetailViewModel.timeline.restartWithEventId(null)
} else {
layoutManager.scrollToPosition(0)
}
layoutManager.scrollToPosition(0)
}
jumpToBottomViewVisibilityManager = JumpToBottomViewVisibilityManager(

View file

@ -27,12 +27,22 @@ class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager,
private val timelineEventController: TimelineEventController) : DefaultListUpdateCallback {
private val newTimelineEventIds = CopyOnWriteArrayList<String>()
private var forceScroll = false
fun addNewTimelineEventIds(eventIds: List<String>) {
newTimelineEventIds.addAll(0, eventIds)
}
fun forceScrollOnNextUpdate() {
forceScroll = true
}
override fun onInserted(position: Int, count: Int) {
if (forceScroll) {
forceScroll = false
layoutManager.scrollToPosition(position)
return
}
Timber.v("On inserted $count count at position: $position")
if (layoutManager.findFirstVisibleItemPosition() != position) {
return