forked from GitHub-Mirror/riotX-android
Continue to work on timeline/pagination. WIP
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
package im.vector.matrix.android.internal.database.query
|
||||
|
||||
import im.vector.matrix.android.internal.database.DBConstants
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
@ -40,5 +41,7 @@ fun ChunkEntity.Companion.findLastFromRoom(realm: Realm, roomId: String): ChunkE
|
||||
fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List<String>): RealmResults<ChunkEntity> {
|
||||
return realm.where(ChunkEntity::class.java)
|
||||
.`in`("events.eventId", eventIds.toTypedArray())
|
||||
.notEqualTo("prevToken", DBConstants.STATE_EVENTS_CHUNK_TOKEN)
|
||||
.notEqualTo("nextToken", DBConstants.STATE_EVENTS_CHUNK_TOKEN)
|
||||
.findAll()
|
||||
}
|
@ -11,11 +11,15 @@ fun EventEntity.Companion.where(realm: Realm, roomId: String): RealmQuery<EventE
|
||||
.equalTo("chunk.room.roomId", roomId)
|
||||
}
|
||||
|
||||
fun EventEntity.Companion.where(realm: Realm, chunk: ChunkEntity): RealmQuery<EventEntity> {
|
||||
return realm.where(EventEntity::class.java)
|
||||
.equalTo("chunk.prevToken", chunk.prevToken)
|
||||
.and()
|
||||
.equalTo("chunk.nextToken", chunk.nextToken)
|
||||
fun EventEntity.Companion.where(realm: Realm, chunk: ChunkEntity?): RealmQuery<EventEntity> {
|
||||
var query = realm.where(EventEntity::class.java)
|
||||
if (chunk?.prevToken != null) {
|
||||
query = query.equalTo("chunk.prevToken", chunk.prevToken)
|
||||
}
|
||||
if (chunk?.nextToken != null) {
|
||||
query = query.equalTo("chunk.nextToken", chunk.nextToken)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
fun RealmResults<EventEntity>.getLast(type: String? = null): EventEntity? {
|
||||
@ -23,5 +27,5 @@ fun RealmResults<EventEntity>.getLast(type: String? = null): EventEntity? {
|
||||
if (type != null) {
|
||||
query = query.equalTo("type", type)
|
||||
}
|
||||
return query.findAll().sort("age").last(null)
|
||||
return query.findAll().last(null)
|
||||
}
|
@ -5,7 +5,9 @@ import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
|
||||
|
||||
object MoshiProvider {
|
||||
|
||||
private val moshi: Moshi = Moshi.Builder().add(UriMoshiAdapter()).build()
|
||||
private val moshi: Moshi = Moshi.Builder()
|
||||
.add(UriMoshiAdapter())
|
||||
.build()
|
||||
|
||||
fun providesMoshi(): Moshi {
|
||||
return moshi
|
||||
|
@ -8,7 +8,6 @@ import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.internal.database.mapper.EventMapper
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
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.TimelineBoundaryCallback
|
||||
@ -26,15 +25,17 @@ data class DefaultRoom(
|
||||
|
||||
override fun liveTimeline(): LiveData<PagedList<Event>> {
|
||||
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||
val lastChunk = ChunkEntity.where(realm, roomId).findAll().last(null)
|
||||
if (lastChunk == null) {
|
||||
EventEntity.where(realm, roomId)
|
||||
} else {
|
||||
EventEntity.where(realm, lastChunk)
|
||||
}
|
||||
ChunkEntity.where(realm, roomId).findAll().last(null).let { it?.events }?.where()
|
||||
}
|
||||
val domainSourceFactory = realmDataSourceFactory.map { EventMapper.map(it) }
|
||||
val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, 20).setBoundaryCallback(boundaryCallback)
|
||||
|
||||
val pagedListConfig = PagedList.Config.Builder()
|
||||
.setEnablePlaceholders(false)
|
||||
.setPageSize(10)
|
||||
.setPrefetchDistance(5)
|
||||
.build()
|
||||
|
||||
val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, pagedListConfig).setBoundaryCallback(boundaryCallback)
|
||||
return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package im.vector.matrix.android.internal.session.room
|
||||
|
||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||
import im.vector.matrix.android.internal.session.room.model.TokenChunkEvent
|
||||
import kotlinx.coroutines.Deferred
|
||||
import retrofit2.Response
|
||||
@ -18,12 +19,13 @@ interface RoomAPI {
|
||||
* @param limit the maximum number of messages to retrieve. Optional.
|
||||
* @param filter A JSON RoomEventFilter to filter returned events with. Optional.
|
||||
*/
|
||||
@GET("rooms/{roomId}/messages")
|
||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/messages")
|
||||
fun getRoomMessagesFrom(@Path("roomId") roomId: String,
|
||||
@Query("from") from: String,
|
||||
@Query("dir") dir: String,
|
||||
@Query("limit") limit: Int,
|
||||
@Query("filter") filter: String?): Deferred<Response<TokenChunkEvent>>
|
||||
@Query("filter") filter: String?
|
||||
): Deferred<Response<TokenChunkEvent>>
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package im.vector.matrix.android.internal.session.room.model
|
||||
|
||||
enum class PaginationDirection(val value: String) {
|
||||
/**
|
||||
* Forwards when the event is added to the end of the timeline.
|
||||
* These events come from the /sync stream or from forwards pagination.
|
||||
*/
|
||||
FORWARDS("f"),
|
||||
|
||||
/**
|
||||
* Backwards when the event is added to the start of the timeline.
|
||||
* These events come from a back pagination.
|
||||
*/
|
||||
BACKWARDS("b")
|
||||
}
|
@ -6,8 +6,8 @@ import im.vector.matrix.android.api.session.events.model.Event
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TokenChunkEvent(
|
||||
@Json(name = "start") val prevToken: String? = null,
|
||||
@Json(name = "end") val nextToken: String? = null,
|
||||
@Json(name = "chunks") val chunk: List<Event> = emptyList(),
|
||||
@Json(name = "start") val nextToken: String? = null,
|
||||
@Json(name = "end") val prevToken: String? = null,
|
||||
@Json(name = "chunk") val chunk: List<Event> = emptyList(),
|
||||
@Json(name = "state") val stateEvents: List<Event> = emptyList()
|
||||
)
|
@ -9,6 +9,7 @@ import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
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.EventEntity
|
||||
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.findWithNextToken
|
||||
@ -16,6 +17,7 @@ import im.vector.matrix.android.internal.database.query.findWithPrevToken
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
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.model.PaginationDirection
|
||||
import im.vector.matrix.android.internal.session.room.model.TokenChunkEvent
|
||||
import im.vector.matrix.android.internal.util.CancelableCoroutine
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
@ -28,8 +30,8 @@ class PaginationRequest(private val roomAPI: RoomAPI,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers) {
|
||||
|
||||
fun execute(roomId: String,
|
||||
from: String? = null,
|
||||
direction: String,
|
||||
from: String?,
|
||||
direction: PaginationDirection,
|
||||
limit: Int = 10,
|
||||
filter: String? = null,
|
||||
callback: MatrixCallback<TokenChunkEvent>
|
||||
@ -42,22 +44,24 @@ class PaginationRequest(private val roomAPI: RoomAPI,
|
||||
}
|
||||
|
||||
private suspend fun execute(roomId: String,
|
||||
from: String? = null,
|
||||
direction: String,
|
||||
from: String?,
|
||||
direction: PaginationDirection,
|
||||
limit: Int = 10,
|
||||
filter: String? = null) = withContext(coroutineDispatchers.io) {
|
||||
filter: String?) = withContext(coroutineDispatchers.io) {
|
||||
|
||||
if (from == null) {
|
||||
return@withContext Either.left(Failure.Unknown(RuntimeException("From token can't be null")))
|
||||
return@withContext Either.left(
|
||||
Failure.Unknown(RuntimeException("From token shouldn't be null"))
|
||||
)
|
||||
}
|
||||
executeRequest<TokenChunkEvent> {
|
||||
apiCall = roomAPI.getRoomMessagesFrom(roomId, from, direction, limit, filter)
|
||||
return@withContext executeRequest<TokenChunkEvent> {
|
||||
apiCall = roomAPI.getRoomMessagesFrom(roomId, from, direction.value, limit, filter)
|
||||
}.leftIfNull {
|
||||
Failure.Unknown(RuntimeException("TokenChunkEvent shouldn't be null"))
|
||||
}.flatMap {
|
||||
}.flatMap { chunk ->
|
||||
try {
|
||||
insertInDb(it, roomId)
|
||||
Either.right(it)
|
||||
insertInDb(chunk, roomId)
|
||||
Either.right(chunk)
|
||||
} catch (exception: Exception) {
|
||||
Either.Left(Failure.Unknown(exception))
|
||||
}
|
||||
@ -67,7 +71,7 @@ class PaginationRequest(private val roomAPI: RoomAPI,
|
||||
private fun insertInDb(chunkEvent: TokenChunkEvent, roomId: String) {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
||||
?: return@runTransactionSync
|
||||
?: return@runTransactionSync
|
||||
|
||||
val nextChunk = ChunkEntity.findWithPrevToken(realm, roomId, chunkEvent.nextToken)
|
||||
val prevChunk = ChunkEntity.findWithNextToken(realm, roomId, chunkEvent.prevToken)
|
||||
@ -75,38 +79,42 @@ class PaginationRequest(private val roomAPI: RoomAPI,
|
||||
val mergedEvents = chunkEvent.chunk + chunkEvent.stateEvents
|
||||
val mergedEventIds = mergedEvents.filter { it.eventId != null }.map { it.eventId!! }
|
||||
val chunksOverlapped = ChunkEntity.findAllIncludingEvents(realm, mergedEventIds)
|
||||
val hasOverlapped = chunksOverlapped.isNotEmpty()
|
||||
|
||||
val currentChunk: ChunkEntity
|
||||
if (nextChunk != null) {
|
||||
currentChunk = nextChunk
|
||||
val currentChunk = if (nextChunk != null) {
|
||||
nextChunk
|
||||
} else {
|
||||
currentChunk = ChunkEntity()
|
||||
ChunkEntity()
|
||||
}
|
||||
|
||||
|
||||
val eventsToAdd = ArrayList<EventEntity>()
|
||||
|
||||
currentChunk.prevToken = chunkEvent.prevToken
|
||||
mergedEvents.forEach { event ->
|
||||
val eventEntity = event.asEntity().let {
|
||||
realm.copyToRealmOrUpdate(it)
|
||||
}
|
||||
if (!currentChunk.events.contains(eventEntity)) {
|
||||
currentChunk.events.add(eventEntity)
|
||||
eventsToAdd.add(0, eventEntity)
|
||||
}
|
||||
}
|
||||
|
||||
if (prevChunk != null) {
|
||||
currentChunk.events.addAll(prevChunk.events)
|
||||
eventsToAdd.addAll(0, prevChunk.events)
|
||||
roomEntity.chunks.remove(prevChunk)
|
||||
|
||||
} else if (chunksOverlapped.isNotEmpty()) {
|
||||
} else if (hasOverlapped) {
|
||||
chunksOverlapped.forEach { chunk ->
|
||||
chunk.events.forEach { event ->
|
||||
if (!currentChunk.events.contains(event)) {
|
||||
currentChunk.events.add(event)
|
||||
eventsToAdd.add(0, event)
|
||||
}
|
||||
}
|
||||
roomEntity.chunks.remove(chunk)
|
||||
}
|
||||
}
|
||||
|
||||
currentChunk.events.addAll(0, eventsToAdd)
|
||||
if (!roomEntity.chunks.contains(currentChunk)) {
|
||||
roomEntity.chunks.add(currentChunk)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
|
||||
import im.vector.matrix.android.internal.session.room.model.PaginationDirection
|
||||
import im.vector.matrix.android.internal.session.room.model.TokenChunkEvent
|
||||
import im.vector.matrix.android.internal.util.PagingRequestHelper
|
||||
import java.util.*
|
||||
@ -25,19 +26,20 @@ class TimelineBoundaryCallback(private val paginationRequest: PaginationRequest,
|
||||
}
|
||||
|
||||
override fun onItemAtEndLoaded(itemAtEnd: Event) {
|
||||
helper.runIfNotRunning(PagingRequestHelper.RequestType.AFTER) {
|
||||
monarchy.doWithRealm { realm ->
|
||||
if (itemAtEnd.eventId == null) {
|
||||
return@doWithRealm
|
||||
}
|
||||
val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(itemAtEnd.eventId)).firstOrNull()
|
||||
paginationRequest.execute(roomId, chunkEntity?.prevToken, "forward", callback = createCallback(it))
|
||||
}
|
||||
}
|
||||
//Todo handle forward pagination
|
||||
}
|
||||
|
||||
override fun onItemAtFrontLoaded(itemAtFront: Event) {
|
||||
//Todo handle forward pagination
|
||||
helper.runIfNotRunning(PagingRequestHelper.RequestType.BEFORE) {
|
||||
monarchy.doWithRealm { realm ->
|
||||
if (itemAtFront.eventId == null) {
|
||||
return@doWithRealm
|
||||
}
|
||||
val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(itemAtFront.eventId)).firstOrNull()
|
||||
paginationRequest.execute(roomId, chunkEntity?.prevToken, PaginationDirection.BACKWARDS, callback = createCallback(it))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun createCallback(pagingRequestCallback: PagingRequestHelper.Request.Callback) = object : MatrixCallback<TokenChunkEvent> {
|
||||
|
Reference in New Issue
Block a user