Timeline : introduce timeline data class to allow listening for isLoadingForward and isLoadingBackward

This commit is contained in:
ganfra
2019-01-07 19:38:36 +01:00
parent f5d64a5707
commit 1269715b5c
16 changed files with 192 additions and 94 deletions

View File

@ -3,9 +3,10 @@ package im.vector.matrix.android.api.session.room
import android.arch.lifecycle.LiveData
import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.api.util.Cancelable
interface Room : TimelineHolder, SendService {
interface Room : TimelineService, SendService {
val roomId: String

View File

@ -1,11 +0,0 @@
package im.vector.matrix.android.api.session.room
import android.arch.lifecycle.LiveData
import android.arch.paging.PagedList
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
interface TimelineHolder {
fun timeline(eventId: String? = null): LiveData<PagedList<EnrichedEvent>>
}

View File

@ -0,0 +1,10 @@
package im.vector.matrix.android.api.session.room.timeline
import android.arch.paging.PagedList
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
data class TimelineData(
val events: PagedList<EnrichedEvent>,
val isLoadingForward: Boolean = false,
val isLoadingBackward: Boolean = false
)

View File

@ -0,0 +1,9 @@
package im.vector.matrix.android.api.session.room.timeline
import android.arch.lifecycle.LiveData
interface TimelineService {
fun timeline(eventId: String? = null): LiveData<TimelineData>
}

View File

@ -9,10 +9,11 @@ import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.SendService
import im.vector.matrix.android.api.session.room.TimelineHolder
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.timeline.TimelineData
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
@ -32,7 +33,7 @@ internal data class DefaultRoom(
private val loadRoomMembersTask by inject<LoadRoomMembersTask>()
private val monarchy by inject<Monarchy>()
private val timelineHolder by inject<TimelineHolder> { parametersOf(roomId) }
private val timelineService by inject<TimelineService> { parametersOf(roomId) }
private val sendService by inject<SendService> { parametersOf(roomId) }
private val taskExecutor by inject<TaskExecutor>()
@ -47,8 +48,8 @@ internal data class DefaultRoom(
}
}
override fun timeline(eventId: String?): LiveData<PagedList<EnrichedEvent>> {
return timelineHolder.timeline(eventId)
override fun timeline(eventId: String?): LiveData<TimelineData> {
return timelineService.timeline(eventId)
}
override fun loadRoomMembersIfNeeded(): Cancelable {

View File

@ -2,14 +2,20 @@ package im.vector.matrix.android.internal.session.room
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.room.SendService
import im.vector.matrix.android.api.session.room.TimelineHolder
import im.vector.matrix.android.api.session.room.send.EventFactory
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.internal.session.DefaultSession
import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
import im.vector.matrix.android.internal.session.room.timeline.*
import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
import im.vector.matrix.android.internal.util.PagingRequestHelper
import org.koin.dsl.module.module
import retrofit2.Retrofit
@ -50,7 +56,7 @@ class RoomModule {
val helper = PagingRequestHelper(Executors.newSingleThreadExecutor())
val timelineBoundaryCallback = TimelineBoundaryCallback(roomId, get(), get(), get(), helper)
val roomMemberExtractor = RoomMemberExtractor(get(), roomId)
DefaultTimelineHolder(roomId, get(), get(), timelineBoundaryCallback, get(), roomMemberExtractor) as TimelineHolder
DefaultTimelineService(roomId, get(), get(), timelineBoundaryCallback, get(), roomMemberExtractor) as TimelineService
}
factory { (roomId: String) ->

View File

@ -1,10 +1,9 @@
package im.vector.matrix.android.internal.session.room.timeline
import arrow.core.Try
import arrow.core.failure
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.FilterUtil
@ -12,7 +11,7 @@ internal interface PaginationTask : Task<PaginationTask.Params, TokenChunkEvent>
data class Params(
val roomId: String,
val from: String?,
val from: String,
val direction: PaginationDirection,
val limit: Int
)
@ -24,9 +23,6 @@ internal class DefaultPaginationTask(private val roomAPI: RoomAPI,
) : PaginationTask {
override fun execute(params: PaginationTask.Params): Try<TokenChunkEvent> {
if (params.from == null) {
return RuntimeException("From token shouldn't be null").failure()
}
val filter = FilterUtil.createRoomEventFilter(true)?.toJSONString()
return executeRequest<PaginationResponse> {
apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter)
@ -36,4 +32,5 @@ internal class DefaultPaginationTask(private val roomAPI: RoomAPI,
.map { chunk }
}
}
}

View File

@ -6,7 +6,8 @@ import android.arch.paging.PagedList
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInterceptor
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.room.TimelineHolder
import im.vector.matrix.android.api.session.room.timeline.TimelineData
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
import im.vector.matrix.android.internal.database.model.EventEntity
@ -15,23 +16,25 @@ import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.LiveDataUtils
import im.vector.matrix.android.internal.util.PagingRequestHelper
import im.vector.matrix.android.internal.util.tryTransactionAsync
import io.realm.Realm
import io.realm.RealmQuery
private const val PAGE_SIZE = 30
internal class DefaultTimelineHolder(private val roomId: String,
private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor,
private val boundaryCallback: TimelineBoundaryCallback,
private val contextOfEventTask: GetContextOfEventTask,
private val roomMemberExtractor: RoomMemberExtractor
) : TimelineHolder {
internal class DefaultTimelineService(private val roomId: String,
private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor,
private val boundaryCallback: TimelineBoundaryCallback,
private val contextOfEventTask: GetContextOfEventTask,
private val roomMemberExtractor: RoomMemberExtractor
) : TimelineService {
private val eventInterceptors = ArrayList<EnrichedEventInterceptor>()
override fun timeline(eventId: String?): LiveData<PagedList<EnrichedEvent>> {
override fun timeline(eventId: String?): LiveData<TimelineData> {
clearUnlinkedEvents()
if (eventId != null) {
fetchEventIfNeeded(eventId)
@ -51,9 +54,16 @@ internal class DefaultTimelineHolder(private val roomId: String,
.build()
val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, pagedListConfig).setBoundaryCallback(boundaryCallback)
return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
val eventsLiveData = monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
return LiveDataUtils.combine(eventsLiveData, boundaryCallback.status) { events, status ->
val isLoadingForward = status.before == PagingRequestHelper.Status.RUNNING
val isLoadingBackward = status.after == PagingRequestHelper.Status.RUNNING
TimelineData(events, isLoadingForward, isLoadingBackward)
}
}
private fun clearUnlinkedEvents() {
monarchy.tryTransactionAsync { realm ->
val unlinkedEvents = EventEntity

View File

@ -1,5 +1,6 @@
package im.vector.matrix.android.internal.session.room.timeline
import android.arch.lifecycle.LiveData
import android.arch.paging.PagedList
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
@ -20,35 +21,60 @@ internal class TimelineBoundaryCallback(private val roomId: String,
var limit = 30
val status = object : LiveData<PagingRequestHelper.StatusReport>() {
init {
value = PagingRequestHelper.StatusReport.createDefault()
}
val listener = PagingRequestHelper.Listener { postValue(it) }
override fun onActive() {
helper.addListener(listener)
}
override fun onInactive() {
helper.removeListener(listener)
}
}
override fun onZeroItemsLoaded() {
// actually, it's not possible
}
override fun onItemAtEndLoaded(itemAtEnd: EnrichedEvent) {
val token = itemAtEnd.root.eventId?.let { getToken(it, PaginationDirection.BACKWARDS) }
?: return
helper.runIfNotRunning(PagingRequestHelper.RequestType.AFTER) {
runPaginationRequest(it, itemAtEnd, PaginationDirection.BACKWARDS)
runPaginationRequest(it, token, PaginationDirection.BACKWARDS)
}
}
override fun onItemAtFrontLoaded(itemAtFront: EnrichedEvent) {
val token = itemAtFront.root.eventId?.let { getToken(it, PaginationDirection.FORWARDS) }
?: return
helper.runIfNotRunning(PagingRequestHelper.RequestType.BEFORE) {
runPaginationRequest(it, itemAtFront, PaginationDirection.FORWARDS)
runPaginationRequest(it, token, PaginationDirection.FORWARDS)
}
}
private fun runPaginationRequest(requestCallback: PagingRequestHelper.Request.Callback,
item: EnrichedEvent,
direction: PaginationDirection) {
private fun getToken(eventId: String, direction: PaginationDirection): String? {
var token: String? = null
monarchy.doWithRealm { realm ->
if (item.root.eventId == null) {
return@doWithRealm
}
val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(item.root.eventId)).firstOrNull()
val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(eventId)).firstOrNull()
token = if (direction == PaginationDirection.FORWARDS) chunkEntity?.nextToken else chunkEntity?.prevToken
}
return token
}
private fun runPaginationRequest(requestCallback: PagingRequestHelper.Request.Callback,
from: String,
direction: PaginationDirection) {
val params = PaginationTask.Params(roomId = roomId,
from = token,
from = from,
direction = direction,
limit = limit)

View File

@ -0,0 +1,38 @@
package im.vector.matrix.android.internal.util
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MediatorLiveData
object LiveDataUtils {
fun <FIRST, SECOND, OUT> combine(firstSource: LiveData<FIRST>,
secondSource: LiveData<SECOND>,
mapper: (FIRST, SECOND) -> OUT): LiveData<OUT> {
return MediatorLiveData<OUT>().apply {
var firstValue: FIRST? = null
var secondValue: SECOND? = null
val valueDispatcher = {
firstValue?.let { safeFirst ->
secondValue?.let { safeSecond ->
val mappedValue = mapper(safeFirst, safeSecond)
postValue(mappedValue)
}
}
}
addSource(firstSource) {
firstValue = it
valueDispatcher()
}
addSource(secondSource) {
secondValue = it
valueDispatcher()
}
}
}
}

View File

@ -379,6 +379,11 @@ public class PagingRequestHelper {
@NonNull
private final Throwable[] mErrors;
public static StatusReport createDefault() {
final Throwable[] errors = {};
return new StatusReport(Status.SUCCESS, Status.SUCCESS, Status.SUCCESS, errors);
}
StatusReport(@NonNull Status initial, @NonNull Status before, @NonNull Status after,
@NonNull Throwable[] errors) {
this.initial = initial;