mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 03:48:12 +03:00
Timeline : introduce timeline data class to allow listening for isLoadingForward and isLoadingBackward
This commit is contained in:
parent
f5d64a5707
commit
1269715b5c
16 changed files with 192 additions and 94 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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>>
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
)
|
|
@ -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>
|
||||||
|
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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) ->
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue