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

@ -6,9 +6,9 @@ import android.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.RiotFragment
import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.core.platform.ToolbarConfigurable
@ -19,7 +19,6 @@ import kotlinx.android.parcel.Parcelize
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
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import timber.log.Timber
@Parcelize @Parcelize
data class RoomDetailArgs( data class RoomDetailArgs(
@ -72,7 +71,7 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
private fun setupRecyclerView() { private fun setupRecyclerView() {
val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true) val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)
//scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager) scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager)
recyclerView.layoutManager = layoutManager recyclerView.layoutManager = layoutManager
//timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) } //timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) }
recyclerView.setHasFixedSize(true) recyclerView.setHasFixedSize(true)
@ -82,16 +81,19 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
} }
private fun renderState(state: RoomDetailViewState) { private fun renderState(state: RoomDetailViewState) {
Timber.v("Render state") renderRoomSummary(state)
val timeline = state.asyncTimeline() renderTimeline(state)
if (timeline != null) {
renderTimeline(timeline)
}
renderRoomSummary(state.asyncRoomSummary())
} }
private fun renderRoomSummary(roomSummary: RoomSummary?) { private fun renderTimeline(state: RoomDetailViewState) {
roomSummary?.let { when (state.asyncTimelineData) {
is Success -> timelineEventController.update(state.asyncTimelineData())
}
}
private fun renderRoomSummary(state: RoomDetailViewState) {
state.asyncRoomSummary()?.let {
toolbarTitleView.text = it.displayName toolbarTitleView.text = it.displayName
AvatarRenderer.render(it, toolbarAvatarImageView) AvatarRenderer.render(it, toolbarAvatarImageView)
if (it.topic.isNotEmpty()) { if (it.topic.isNotEmpty()) {
@ -103,11 +105,6 @@ class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
} }
} }
private fun renderTimeline(timeline: Timeline?) {
//scrollOnNewMessageCallback.hasBeenUpdated.set(true)
timelineEventController.timeline = timeline
}
// TimelineEventController.Callback ************************************************************ // TimelineEventController.Callback ************************************************************
override fun onUrlClicked(url: String) { override fun onUrlClicked(url: String) {

View file

@ -53,9 +53,10 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
private fun observeTimeline() { private fun observeTimeline() {
room.rx().timeline(eventId) room.rx().timeline(eventId)
.execute { asyncTimeline -> .execute { timelineData ->
copy(asyncTimeline = asyncTimeline) copy(asyncTimelineData= timelineData)
} }
} }
} }

View file

@ -1,19 +1,16 @@
package im.vector.riotredesign.features.home.room.detail package im.vector.riotredesign.features.home.room.detail
import android.arch.paging.PagedList
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.timeline.TimelineData
typealias Timeline = PagedList<EnrichedEvent>
data class RoomDetailViewState( data class RoomDetailViewState(
val roomId: String, val roomId: String,
val eventId: String?, val eventId: String?,
val asyncRoomSummary: Async<RoomSummary> = Uninitialized, val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
val asyncTimeline: Async<Timeline> = Uninitialized val asyncTimelineData: Async<TimelineData> = Uninitialized
) : MvRxState { ) : MvRxState {
constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId) constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId)

View file

@ -5,9 +5,9 @@ import com.airbnb.epoxy.EpoxyAsyncUtil
import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyController
import im.vector.matrix.android.api.session.events.model.EnrichedEvent import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.timeline.TimelineData
import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.features.home.LoadingItemModel_ import im.vector.riotredesign.features.home.LoadingItemModel_
import im.vector.riotredesign.features.home.room.detail.Timeline
class TimelineEventController(private val roomId: String, class TimelineEventController(private val roomId: String,
private val messageItemFactory: MessageItemFactory, private val messageItemFactory: MessageItemFactory,
@ -36,28 +36,38 @@ class TimelineEventController(private val roomId: String,
} }
} }
private var snapshotList: List<EnrichedEvent>? = emptyList() private var snapshotList: List<EnrichedEvent> = emptyList()
var timeline: Timeline? = null private var timelineData: TimelineData? = null
set(value) {
field?.removeWeakCallback(pagedListCallback)
field = value
field?.addWeakCallback(null, pagedListCallback)
buildSnapshotList()
}
var callback: Callback? = null var callback: Callback? = null
override fun buildModels() { fun update(timelineData: TimelineData?) {
buildModels(snapshotList) timelineData?.events?.removeWeakCallback(pagedListCallback)
this.timelineData = timelineData
timelineData?.events?.addWeakCallback(null, pagedListCallback)
buildSnapshotList()
} }
private fun buildModels(data: List<EnrichedEvent?>?) { override fun buildModels() {
if (data.isNullOrEmpty()) { buildModelsWith(
snapshotList,
timelineData?.isLoadingForward ?: false,
timelineData?.isLoadingBackward ?: false
)
}
private fun buildModelsWith(events: List<EnrichedEvent?>,
isLoadingForward: Boolean,
isLoadingBackward: Boolean) {
if (events.isEmpty()) {
return return
} }
for (index in 0 until data.size) { LoadingItemModel_()
val event = data[index] ?: continue .id(roomId + "forward_loading_item")
val nextEvent = if (index + 1 < data.size) data[index + 1] else null .addIf(isLoadingForward, this)
for (index in 0 until events.size) {
val event = events[index] ?: continue
val nextEvent = if (index + 1 < events.size) events[index + 1] else null
val date = event.root.localDateTime() val date = event.root.localDateTime()
val nextDate = nextEvent?.root?.localDateTime() val nextDate = nextEvent?.root?.localDateTime()
@ -65,10 +75,13 @@ class TimelineEventController(private val roomId: String,
val item = when (event.root.type) { val item = when (event.root.type) {
EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, addDaySeparator, date, callback) EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, addDaySeparator, date, callback)
else -> textItemFactory.create(event) else -> textItemFactory.create(event)
} }
item item
?.onBind { timeline?.loadAround(index) } ?.onBind {
timelineData?.events?.loadAround(index)
}
?.id(event.localId) ?.id(event.localId)
?.addTo(this) ?.addTo(this)
@ -78,15 +91,14 @@ class TimelineEventController(private val roomId: String,
} }
} }
//It's a hack at the moment
val isLastEvent = data.last()?.root?.type == EventType.STATE_ROOM_CREATE
LoadingItemModel_() LoadingItemModel_()
.id(roomId + "backward_loading_item") .id(roomId + "backward_loading_item")
.addIf(!isLastEvent, this) .addIf(isLoadingBackward, this)
} }
private fun buildSnapshotList() { private fun buildSnapshotList() {
snapshotList = timeline?.snapshot() ?: emptyList() snapshotList = timelineData?.events?.snapshot() ?: emptyList()
requestModelBuild() requestModelBuild()
} }

View file

@ -1,9 +1,8 @@
package im.vector.matrix.rx package im.vector.matrix.rx
import android.arch.paging.PagedList
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.timeline.TimelineData
import io.reactivex.Observable import io.reactivex.Observable
class RxRoom(private val room: Room) { class RxRoom(private val room: Room) {
@ -12,7 +11,7 @@ class RxRoom(private val room: Room) {
return room.roomSummary.asObservable() return room.roomSummary.asObservable()
} }
fun timeline(eventId: String? = null): Observable<PagedList<EnrichedEvent>> { fun timeline(eventId: String? = null): Observable<TimelineData> {
return room.timeline(eventId).asObservable() return room.timeline(eventId).asObservable()
} }

View file

@ -3,9 +3,10 @@ package im.vector.matrix.android.api.session.room
import android.arch.lifecycle.LiveData import android.arch.lifecycle.LiveData
import im.vector.matrix.android.api.session.room.model.MyMembership 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.model.RoomSummary
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
interface Room : TimelineHolder, SendService { interface Room : TimelineService, SendService {
val roomId: String 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.events.model.Event
import im.vector.matrix.android.api.session.room.Room 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.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.Membership
import im.vector.matrix.android.api.session.room.model.MyMembership 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.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.api.util.Cancelable
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity 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 loadRoomMembersTask by inject<LoadRoomMembersTask>()
private val monarchy by inject<Monarchy>() 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 sendService by inject<SendService> { parametersOf(roomId) }
private val taskExecutor by inject<TaskExecutor>() private val taskExecutor by inject<TaskExecutor>()
@ -47,8 +48,8 @@ internal data class DefaultRoom(
} }
} }
override fun timeline(eventId: String?): LiveData<PagedList<EnrichedEvent>> { override fun timeline(eventId: String?): LiveData<TimelineData> {
return timelineHolder.timeline(eventId) return timelineService.timeline(eventId)
} }
override fun loadRoomMembersIfNeeded(): Cancelable { 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.auth.data.SessionParams
import im.vector.matrix.android.api.session.room.SendService 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.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.DefaultSession
import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask 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.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor 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.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 im.vector.matrix.android.internal.util.PagingRequestHelper
import org.koin.dsl.module.module import org.koin.dsl.module.module
import retrofit2.Retrofit import retrofit2.Retrofit
@ -50,7 +56,7 @@ class RoomModule {
val helper = PagingRequestHelper(Executors.newSingleThreadExecutor()) val helper = PagingRequestHelper(Executors.newSingleThreadExecutor())
val timelineBoundaryCallback = TimelineBoundaryCallback(roomId, get(), get(), get(), helper) val timelineBoundaryCallback = TimelineBoundaryCallback(roomId, get(), get(), get(), helper)
val roomMemberExtractor = RoomMemberExtractor(get(), roomId) 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) -> factory { (roomId: String) ->

View file

@ -1,10 +1,9 @@
package im.vector.matrix.android.internal.session.room.timeline package im.vector.matrix.android.internal.session.room.timeline
import arrow.core.Try 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.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.task.Task
import im.vector.matrix.android.internal.util.FilterUtil import im.vector.matrix.android.internal.util.FilterUtil
@ -12,7 +11,7 @@ internal interface PaginationTask : Task<PaginationTask.Params, TokenChunkEvent>
data class Params( data class Params(
val roomId: String, val roomId: String,
val from: String?, val from: String,
val direction: PaginationDirection, val direction: PaginationDirection,
val limit: Int val limit: Int
) )
@ -24,9 +23,6 @@ internal class DefaultPaginationTask(private val roomAPI: RoomAPI,
) : PaginationTask { ) : PaginationTask {
override fun execute(params: PaginationTask.Params): Try<TokenChunkEvent> { 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() val filter = FilterUtil.createRoomEventFilter(true)?.toJSONString()
return executeRequest<PaginationResponse> { return executeRequest<PaginationResponse> {
apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) 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 } .map { chunk }
} }
} }
} }

View file

@ -6,7 +6,8 @@ import android.arch.paging.PagedList
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInterceptor 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.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.mapper.asDomain
import im.vector.matrix.android.internal.database.model.ChunkEntityFields import im.vector.matrix.android.internal.database.model.ChunkEntityFields
import im.vector.matrix.android.internal.database.model.EventEntity 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.session.room.members.RoomMemberExtractor
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith 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 im.vector.matrix.android.internal.util.tryTransactionAsync
import io.realm.Realm import io.realm.Realm
import io.realm.RealmQuery import io.realm.RealmQuery
private const val PAGE_SIZE = 30 private const val PAGE_SIZE = 30
internal class DefaultTimelineHolder(private val roomId: String, internal class DefaultTimelineService(private val roomId: String,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val boundaryCallback: TimelineBoundaryCallback, private val boundaryCallback: TimelineBoundaryCallback,
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val roomMemberExtractor: RoomMemberExtractor private val roomMemberExtractor: RoomMemberExtractor
) : TimelineHolder { ) : TimelineService {
private val eventInterceptors = ArrayList<EnrichedEventInterceptor>() private val eventInterceptors = ArrayList<EnrichedEventInterceptor>()
override fun timeline(eventId: String?): LiveData<PagedList<EnrichedEvent>> { override fun timeline(eventId: String?): LiveData<TimelineData> {
clearUnlinkedEvents() clearUnlinkedEvents()
if (eventId != null) { if (eventId != null) {
fetchEventIfNeeded(eventId) fetchEventIfNeeded(eventId)
@ -51,9 +54,16 @@ internal class DefaultTimelineHolder(private val roomId: String,
.build() .build()
val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, pagedListConfig).setBoundaryCallback(boundaryCallback) 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() { private fun clearUnlinkedEvents() {
monarchy.tryTransactionAsync { realm -> monarchy.tryTransactionAsync { realm ->
val unlinkedEvents = EventEntity val unlinkedEvents = EventEntity

View file

@ -1,5 +1,6 @@
package im.vector.matrix.android.internal.session.room.timeline package im.vector.matrix.android.internal.session.room.timeline
import android.arch.lifecycle.LiveData
import android.arch.paging.PagedList import android.arch.paging.PagedList
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
@ -20,35 +21,60 @@ internal class TimelineBoundaryCallback(private val roomId: String,
var limit = 30 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() { override fun onZeroItemsLoaded() {
// actually, it's not possible // actually, it's not possible
} }
override fun onItemAtEndLoaded(itemAtEnd: EnrichedEvent) { override fun onItemAtEndLoaded(itemAtEnd: EnrichedEvent) {
val token = itemAtEnd.root.eventId?.let { getToken(it, PaginationDirection.BACKWARDS) }
?: return
helper.runIfNotRunning(PagingRequestHelper.RequestType.AFTER) { helper.runIfNotRunning(PagingRequestHelper.RequestType.AFTER) {
runPaginationRequest(it, itemAtEnd, PaginationDirection.BACKWARDS) runPaginationRequest(it, token, PaginationDirection.BACKWARDS)
} }
} }
override fun onItemAtFrontLoaded(itemAtFront: EnrichedEvent) { override fun onItemAtFrontLoaded(itemAtFront: EnrichedEvent) {
val token = itemAtFront.root.eventId?.let { getToken(it, PaginationDirection.FORWARDS) }
?: return
helper.runIfNotRunning(PagingRequestHelper.RequestType.BEFORE) { helper.runIfNotRunning(PagingRequestHelper.RequestType.BEFORE) {
runPaginationRequest(it, itemAtFront, PaginationDirection.FORWARDS) runPaginationRequest(it, token, PaginationDirection.FORWARDS)
} }
} }
private fun runPaginationRequest(requestCallback: PagingRequestHelper.Request.Callback, private fun getToken(eventId: String, direction: PaginationDirection): String? {
item: EnrichedEvent,
direction: PaginationDirection) {
var token: String? = null var token: String? = null
monarchy.doWithRealm { realm -> monarchy.doWithRealm { realm ->
if (item.root.eventId == null) { val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(eventId)).firstOrNull()
return@doWithRealm
}
val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(item.root.eventId)).firstOrNull()
token = if (direction == PaginationDirection.FORWARDS) chunkEntity?.nextToken else chunkEntity?.prevToken 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, val params = PaginationTask.Params(roomId = roomId,
from = token, from = from,
direction = direction, direction = direction,
limit = limit) 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 @NonNull
private final Throwable[] mErrors; 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, StatusReport(@NonNull Status initial, @NonNull Status before, @NonNull Status after,
@NonNull Throwable[] errors) { @NonNull Throwable[] errors) {
this.initial = initial; this.initial = initial;