Makes back pagination work. Still need to refine but ok for proto.

This commit is contained in:
ganfra 2018-10-22 15:17:50 +02:00
parent d71ae02162
commit 0f4d15e488
11 changed files with 56 additions and 37 deletions

View File

@ -17,7 +17,7 @@ import im.vector.riotredesign.core.utils.FragmentArgumentDelegate
import kotlinx.android.synthetic.main.fragment_room_detail.* import kotlinx.android.synthetic.main.fragment_room_detail.*
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject


class RoomDetailFragment : RiotFragment(), TimelineAdapter.Callback { class RoomDetailFragment : RiotFragment(), TimelineEventAdapter.Callback {


companion object { companion object {


@ -31,7 +31,8 @@ class RoomDetailFragment : RiotFragment(), TimelineAdapter.Callback {
private val matrix by inject<Matrix>() private val matrix by inject<Matrix>()
private val currentSession = matrix.currentSession!! private val currentSession = matrix.currentSession!!
private var roomId by FragmentArgumentDelegate<String>() private var roomId by FragmentArgumentDelegate<String>()
private val adapter = TimelineAdapter(this) private val timelineAdapter = TimelineEventAdapter(this)
private val timelineEventController = TimelineEventController()
private lateinit var room: Room private lateinit var room: Room


override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@ -46,26 +47,27 @@ class RoomDetailFragment : RiotFragment(), TimelineAdapter.Callback {
} }


private fun renderEvents(events: PagedList<Event>?) { private fun renderEvents(events: PagedList<Event>?) {
adapter.submitList(events) timelineAdapter.submitList(events)
} }


private fun setupRecyclerView() { private fun setupRecyclerView() {
val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { layoutManager.stackFromEnd = true
timelineAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (layoutManager.findLastCompletelyVisibleItemPosition() == positionStart - itemCount) { if (layoutManager.findFirstVisibleItemPosition() == 0) {
layoutManager.scrollToPosition(adapter.itemCount - 1) layoutManager.scrollToPosition(0)
} }
} }
}) })
recyclerView.layoutManager = layoutManager recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter recyclerView.adapter = timelineAdapter
recyclerView.setHasFixedSize(true) //recyclerView.setController(timelineEventController)
} }


override fun onEventsListChanged(oldList: List<Event>?, newList: List<Event>?) { override fun onEventsListChanged(oldList: List<Event>?, newList: List<Event>?) {
if (oldList == null && newList != null) { if (oldList == null && newList != null) {
recyclerView.scrollToPosition(newList.size - 1) recyclerView.scrollToPosition(0)
} }
} }



View File

@ -14,8 +14,8 @@ import im.vector.riotredesign.R
* Created by francois on 14/05/2018. * Created by francois on 14/05/2018.
*/ */


class TimelineAdapter(private val callback: Callback? = null) class TimelineEventAdapter(private val callback: Callback? = null)
: PagedListAdapter<Event, TimelineAdapter.ViewHolder>(EventDiffUtilCallback()) { : PagedListAdapter<Event, TimelineEventAdapter.ViewHolder>(EventDiffUtilCallback()) {




private var currentList: List<Event>? = null private var currentList: List<Event>? = null

View File

@ -3,7 +3,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">


<android.support.v7.widget.RecyclerView <com.airbnb.epoxy.EpoxyRecyclerView
android:id="@+id/recyclerView" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

View File

@ -7,7 +7,6 @@ import io.realm.annotations.LinkingObjects


open class ChunkEntity(var prevToken: String? = null, open class ChunkEntity(var prevToken: String? = null,
var nextToken: String? = null, var nextToken: String? = null,
var isLimited: Boolean = true,
var events: RealmList<EventEntity> = RealmList() var events: RealmList<EventEntity> = RealmList()
) : RealmObject() { ) : RealmObject() {



View File

@ -33,7 +33,7 @@ fun ChunkEntity.Companion.findWithNextToken(realm: Realm, roomId: String, nextTo
.findFirst() .findFirst()
} }


fun ChunkEntity.Companion.findLastFromRoom(realm: Realm, roomId: String): ChunkEntity? { fun ChunkEntity.Companion.findLastLiveChunkFromRoom(realm: Realm, roomId: String): ChunkEntity? {
return where(realm, roomId) return where(realm, roomId)
.and() .and()
.isNull("nextToken") .isNull("nextToken")

View File

@ -5,6 +5,7 @@ import im.vector.matrix.android.internal.database.model.EventEntity
import io.realm.Realm import io.realm.Realm
import io.realm.RealmQuery import io.realm.RealmQuery
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.Sort


fun EventEntity.Companion.where(realm: Realm, roomId: String): RealmQuery<EventEntity> { fun EventEntity.Companion.where(realm: Realm, roomId: String): RealmQuery<EventEntity> {
return realm.where(EventEntity::class.java) return realm.where(EventEntity::class.java)
@ -23,9 +24,9 @@ fun EventEntity.Companion.where(realm: Realm, chunk: ChunkEntity?): RealmQuery<E
} }


fun RealmResults<EventEntity>.getLast(type: String? = null): EventEntity? { fun RealmResults<EventEntity>.getLast(type: String? = null): EventEntity? {
var query = this.where() var query = this.where().sort("originServerTs", Sort.DESCENDING)
if (type != null) { if (type != null) {
query = query.equalTo("type", type) query = query.equalTo("type", type)
} }
return query.findAll().last(null) return query.findFirst()
} }

View File

@ -11,6 +11,7 @@ import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.room.timeline.PaginationRequest import im.vector.matrix.android.internal.session.room.timeline.PaginationRequest
import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback
import io.realm.Sort
import org.koin.standalone.KoinComponent import org.koin.standalone.KoinComponent
import org.koin.standalone.inject import org.koin.standalone.inject
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -28,17 +29,16 @@ data class DefaultRoom(
ChunkEntity.where(realm, roomId) ChunkEntity.where(realm, roomId)
.findAll() .findAll()
.last(null) .last(null)
?.let { it.events } ?.let {
?.where() it.events.where().sort("originServerTs", Sort.DESCENDING)
?.sort("originServerTs") }
} }
val domainSourceFactory = realmDataSourceFactory.map { EventMapper.map(it) } val domainSourceFactory = realmDataSourceFactory.map { EventMapper.map(it) }


val pagedListConfig = PagedList.Config.Builder() val pagedListConfig = PagedList.Config.Builder()
.setEnablePlaceholders(false) .setEnablePlaceholders(false)
.setPageSize(10) .setPageSize(10)
.setInitialLoadSizeHint(30) .setPrefetchDistance(10)
.setPrefetchDistance(5)
.build() .build()


val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, pagedListConfig).setBoundaryCallback(boundaryCallback) val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, pagedListConfig).setBoundaryCallback(boundaryCallback)

View File

@ -7,6 +7,7 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.DBConstants
import im.vector.matrix.android.internal.database.mapper.asEntity import im.vector.matrix.android.internal.database.mapper.asEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
@ -14,6 +15,7 @@ import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
import im.vector.matrix.android.internal.database.query.findWithNextToken import im.vector.matrix.android.internal.database.query.findWithNextToken
import im.vector.matrix.android.internal.database.query.findWithPrevToken import im.vector.matrix.android.internal.database.query.findWithPrevToken
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.legacy.util.FilterUtil
import im.vector.matrix.android.internal.network.executeRequest 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.RoomAPI
import im.vector.matrix.android.internal.session.room.model.PaginationDirection import im.vector.matrix.android.internal.session.room.model.PaginationDirection
@ -32,10 +34,10 @@ class PaginationRequest(private val roomAPI: RoomAPI,
from: String?, from: String?,
direction: PaginationDirection, direction: PaginationDirection,
limit: Int = 10, limit: Int = 10,
filter: String? = null,
callback: MatrixCallback<TokenChunkEvent> callback: MatrixCallback<TokenChunkEvent>
): Cancelable { ): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) { val job = GlobalScope.launch(coroutineDispatchers.main) {
val filter = FilterUtil.createRoomEventFilter(true)?.toJSONString()
val chunkOrFailure = execute(roomId, from, direction, limit, filter) val chunkOrFailure = execute(roomId, from, direction, limit, filter)
chunkOrFailure.bimap({ callback.onFailure(it) }, { callback.onSuccess(it) }) chunkOrFailure.bimap({ callback.onFailure(it) }, { callback.onSuccess(it) })
} }
@ -70,14 +72,13 @@ class PaginationRequest(private val roomAPI: RoomAPI,
private fun insertInDb(chunkEvent: TokenChunkEvent, roomId: String) { private fun insertInDb(chunkEvent: TokenChunkEvent, roomId: String) {
monarchy.runTransactionSync { realm -> monarchy.runTransactionSync { realm ->
val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: return@runTransactionSync ?: return@runTransactionSync


val nextChunk = ChunkEntity.findWithPrevToken(realm, roomId, chunkEvent.nextToken) val nextChunk = ChunkEntity.findWithPrevToken(realm, roomId, chunkEvent.nextToken)
val prevChunk = ChunkEntity.findWithNextToken(realm, roomId, chunkEvent.prevToken) val prevChunk = ChunkEntity.findWithNextToken(realm, roomId, chunkEvent.prevToken)


val mergedEvents = chunkEvent.chunk + chunkEvent.stateEvents val eventIds = chunkEvent.chunk.filter { it.eventId != null }.map { it.eventId!! }
val mergedEventIds = mergedEvents.filter { it.eventId != null }.map { it.eventId!! } val chunksOverlapped = ChunkEntity.findAllIncludingEvents(realm, eventIds)
val chunksOverlapped = ChunkEntity.findAllIncludingEvents(realm, mergedEventIds)
val hasOverlapped = chunksOverlapped.isNotEmpty() val hasOverlapped = chunksOverlapped.isNotEmpty()


val currentChunk = if (nextChunk != null) { val currentChunk = if (nextChunk != null) {
@ -85,9 +86,25 @@ class PaginationRequest(private val roomAPI: RoomAPI,
} else { } else {
ChunkEntity() ChunkEntity()
} }

currentChunk.prevToken = chunkEvent.prevToken currentChunk.prevToken = chunkEvent.prevToken
mergedEvents.forEach { event ->

val stateEventsChunk = ChunkEntity.findWithNextToken(realm, roomId, DBConstants.STATE_EVENTS_CHUNK_TOKEN)
?: ChunkEntity().apply {
this.prevToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN
this.nextToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN
}

chunkEvent.stateEvents.forEach { stateEvent ->
val eventEntity = stateEvent.asEntity().let {
realm.copyToRealmOrUpdate(it)
}
if (!stateEventsChunk.events.contains(eventEntity)) {
stateEventsChunk.events.add(0, eventEntity)
}
}

chunkEvent.chunk.forEach { event ->
val eventEntity = event.asEntity().let { val eventEntity = event.asEntity().let {
realm.copyToRealmOrUpdate(it) realm.copyToRealmOrUpdate(it)
} }
@ -101,13 +118,14 @@ class PaginationRequest(private val roomAPI: RoomAPI,
roomEntity.chunks.remove(prevChunk) roomEntity.chunks.remove(prevChunk)


} else if (hasOverlapped) { } else if (hasOverlapped) {
chunksOverlapped.forEach { chunk -> chunksOverlapped.forEach { overlapped ->
chunk.events.forEach { event -> overlapped.events.forEach { event ->
if (!currentChunk.events.contains(event)) { if (!currentChunk.events.contains(event)) {
currentChunk.events.add(0, event) currentChunk.events.add(0, event)
} }
} }
roomEntity.chunks.remove(chunk) currentChunk.prevToken = overlapped.prevToken
roomEntity.chunks.remove(overlapped)
} }
} }
if (!roomEntity.chunks.contains(currentChunk)) { if (!roomEntity.chunks.contains(currentChunk)) {

View File

@ -32,7 +32,7 @@ class TimelineBoundaryCallback(private val paginationRequest: PaginationRequest,
return@doWithRealm return@doWithRealm
} }
val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(itemAtEnd.eventId)).firstOrNull() val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(itemAtEnd.eventId)).firstOrNull()
paginationRequest.execute(roomId, chunkEntity?.nextToken, PaginationDirection.FORWARDS, callback = createCallback(it)) paginationRequest.execute(roomId, chunkEntity?.prevToken, PaginationDirection.BACKWARDS, callback = createCallback(it))
} }
} }
} }
@ -44,7 +44,7 @@ class TimelineBoundaryCallback(private val paginationRequest: PaginationRequest,
return@doWithRealm return@doWithRealm
} }
val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(itemAtFront.eventId)).firstOrNull() val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(itemAtFront.eventId)).firstOrNull()
paginationRequest.execute(roomId, chunkEntity?.prevToken, PaginationDirection.BACKWARDS, callback = createCallback(it)) paginationRequest.execute(roomId, chunkEntity?.nextToken, PaginationDirection.FORWARDS, callback = createCallback(it))
} }
} }
} }

View File

@ -7,7 +7,7 @@ import im.vector.matrix.android.internal.database.mapper.asEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.findAllIncludingEvents import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
import im.vector.matrix.android.internal.database.query.findLastFromRoom import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
import im.vector.matrix.android.internal.session.sync.model.RoomSync import im.vector.matrix.android.internal.session.sync.model.RoomSync
@ -95,14 +95,13 @@ class RoomSyncHandler(private val monarchy: Monarchy) {
isLimited: Boolean = true): ChunkEntity { isLimited: Boolean = true): ChunkEntity {


val chunkEntity = if (!isLimited) { val chunkEntity = if (!isLimited) {
ChunkEntity.findLastFromRoom(realm, roomId) ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
} else { } else {
val eventIds = eventList.filter { it.eventId != null }.map { it.eventId!! } val eventIds = eventList.filter { it.eventId != null }.map { it.eventId!! }
ChunkEntity.findAllIncludingEvents(realm, eventIds).firstOrNull() ChunkEntity.findAllIncludingEvents(realm, eventIds).firstOrNull()
} ?: ChunkEntity().apply { this.prevToken = prevToken } } ?: ChunkEntity().apply { this.prevToken = prevToken }


chunkEntity.nextToken = nextToken chunkEntity.nextToken = nextToken
chunkEntity.isLimited = isLimited


eventList.forEach { event -> eventList.forEach { event ->
val eventEntity = event.asEntity().let { val eventEntity = event.asEntity().let {