Timeline: fix some more issues
This commit is contained in:
parent
4a80df082c
commit
0ea878af8a
|
@ -198,9 +198,9 @@ internal class DefaultTimeline(
|
|||
.also { it.addChangeListener(relationsListener) }
|
||||
|
||||
if (settings.buildReadReceipts) {
|
||||
hiddenReadReceipts.start(realm, filteredEvents, this)
|
||||
hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this)
|
||||
}
|
||||
hiddenReadMarker.start(realm, filteredEvents, this)
|
||||
hiddenReadMarker.start(realm, filteredEvents, nonFilteredEvents, this)
|
||||
isReady.set(true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,8 @@ internal class TimelineHiddenReadMarker constructor(private val roomId: String,
|
|||
private var previousDisplayedEventId: String? = null
|
||||
private var hiddenReadMarker: RealmResults<ReadMarkerEntity>? = null
|
||||
|
||||
private lateinit var liveEvents: RealmResults<TimelineEventEntity>
|
||||
private lateinit var filteredEvents: RealmResults<TimelineEventEntity>
|
||||
private lateinit var nonFilteredEvents: RealmResults<TimelineEventEntity>
|
||||
private lateinit var delegate: Delegate
|
||||
|
||||
private val readMarkerListener = OrderedRealmCollectionChangeListener<RealmResults<ReadMarkerEntity>> { readMarkers, changeSet ->
|
||||
|
@ -64,10 +65,11 @@ internal class TimelineHiddenReadMarker constructor(private val roomId: String,
|
|||
val hiddenEvent = readMarker.timelineEvent?.firstOrNull()
|
||||
?: return@OrderedRealmCollectionChangeListener
|
||||
|
||||
val isLoaded = nonFilteredEvents.where().equalTo(TimelineEventEntityFields.EVENT_ID, hiddenEvent.eventId).findFirst() != null
|
||||
val displayIndex = hiddenEvent.root?.displayIndex
|
||||
if (displayIndex != null) {
|
||||
if (isLoaded && displayIndex != null) {
|
||||
// Then we are looking for the first displayable event after the hidden one
|
||||
val firstDisplayedEvent = liveEvents.where()
|
||||
val firstDisplayedEvent = filteredEvents.where()
|
||||
.lessThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, displayIndex)
|
||||
.findFirst()
|
||||
|
||||
|
@ -86,8 +88,12 @@ internal class TimelineHiddenReadMarker constructor(private val roomId: String,
|
|||
/**
|
||||
* Start the realm query subscription. Has to be called on an HandlerThread
|
||||
*/
|
||||
fun start(realm: Realm, liveEvents: RealmResults<TimelineEventEntity>, delegate: Delegate) {
|
||||
this.liveEvents = liveEvents
|
||||
fun start(realm: Realm,
|
||||
filteredEvents: RealmResults<TimelineEventEntity>,
|
||||
nonFilteredEvents: RealmResults<TimelineEventEntity>,
|
||||
delegate: Delegate) {
|
||||
this.filteredEvents = filteredEvents
|
||||
this.nonFilteredEvents = nonFilteredEvents
|
||||
this.delegate = delegate
|
||||
// We are looking for read receipts set on hidden events.
|
||||
// We only accept those with a timelineEvent (so coming from pagination/sync).
|
||||
|
|
|
@ -49,7 +49,8 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu
|
|||
private val correctedReadReceiptsByEvent = HashMap<String, MutableList<ReadReceipt>>()
|
||||
|
||||
private lateinit var hiddenReadReceipts: RealmResults<ReadReceiptsSummaryEntity>
|
||||
private lateinit var liveEvents: RealmResults<TimelineEventEntity>
|
||||
private lateinit var nonFilteredEvents: RealmResults<TimelineEventEntity>
|
||||
private lateinit var filteredEvents: RealmResults<TimelineEventEntity>
|
||||
private lateinit var delegate: Delegate
|
||||
|
||||
private val hiddenReadReceiptsListener = OrderedRealmCollectionChangeListener<RealmResults<ReadReceiptsSummaryEntity>> { collection, changeSet ->
|
||||
|
@ -60,7 +61,7 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu
|
|||
// Deletion here means we don't have any readReceipts for the given hidden events
|
||||
changeSet.deletions.forEach {
|
||||
val eventId = correctedReadReceiptsEventByIndex.get(it, "")
|
||||
val timelineEvent = liveEvents.where()
|
||||
val timelineEvent = filteredEvents.where()
|
||||
.equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
|
||||
.findFirst()
|
||||
|
||||
|
@ -70,12 +71,14 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu
|
|||
}
|
||||
correctedReadReceiptsEventByIndex.clear()
|
||||
correctedReadReceiptsByEvent.clear()
|
||||
hiddenReadReceipts.forEachIndexed { index, summary ->
|
||||
val timelineEvent = summary?.timelineEvent?.firstOrNull()
|
||||
val displayIndex = timelineEvent?.root?.displayIndex
|
||||
if (displayIndex != null) {
|
||||
for (index in 0 until hiddenReadReceipts.size) {
|
||||
val summary = hiddenReadReceipts[index] ?: continue
|
||||
val timelineEvent = summary.timelineEvent?.firstOrNull() ?: continue
|
||||
val isLoaded = nonFilteredEvents.where().equalTo(TimelineEventEntityFields.EVENT_ID, timelineEvent.eventId).findFirst() != null
|
||||
val displayIndex = timelineEvent.root?.displayIndex
|
||||
if (isLoaded && displayIndex != null) {
|
||||
// Then we are looking for the first displayable event after the hidden one
|
||||
val firstDisplayedEvent = liveEvents.where()
|
||||
val firstDisplayedEvent = filteredEvents.where()
|
||||
.lessThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, displayIndex)
|
||||
.findFirst()
|
||||
|
||||
|
@ -106,8 +109,12 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu
|
|||
/**
|
||||
* Start the realm query subscription. Has to be called on an HandlerThread
|
||||
*/
|
||||
fun start(realm: Realm, liveEvents: RealmResults<TimelineEventEntity>, delegate: Delegate) {
|
||||
this.liveEvents = liveEvents
|
||||
fun start(realm: Realm,
|
||||
filteredEvents: RealmResults<TimelineEventEntity>,
|
||||
nonFilteredEvents: RealmResults<TimelineEventEntity>,
|
||||
delegate: Delegate) {
|
||||
this.filteredEvents = filteredEvents
|
||||
this.nonFilteredEvents = nonFilteredEvents
|
||||
this.delegate = delegate
|
||||
// We are looking for read receipts set on hidden events.
|
||||
// We only accept those with a timelineEvent (so coming from pagination/sync).
|
||||
|
|
|
@ -40,14 +40,18 @@ internal class RoomFullyReadHandler @Inject constructor() {
|
|||
readMarkerId = content.eventId
|
||||
}
|
||||
// Remove the old markers if any
|
||||
val oldReadMarkerEvents = TimelineEventEntity.where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.BOTH).isNotNull(TimelineEventEntityFields.READ_MARKER.`$`).findAll()
|
||||
val oldReadMarkerEvents = TimelineEventEntity
|
||||
.where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.BOTH)
|
||||
.isNotNull(TimelineEventEntityFields.READ_MARKER.`$`)
|
||||
.findAll()
|
||||
|
||||
oldReadMarkerEvents.forEach { it.readMarker = null }
|
||||
val readMarkerEntity = ReadMarkerEntity.getOrCreate(realm, roomId).apply {
|
||||
this.eventId = content.eventId
|
||||
}
|
||||
// Attach to timelineEvent if known
|
||||
val timelineEventEntity = TimelineEventEntity.where(realm, roomId = roomId, eventId = content.eventId).findFirst()
|
||||
timelineEventEntity?.readMarker = readMarkerEntity
|
||||
val timelineEventEntities = TimelineEventEntity.where(realm, roomId = roomId, eventId = content.eventId).findAll()
|
||||
timelineEventEntities.forEach { it.readMarker = readMarkerEntity }
|
||||
}
|
||||
|
||||
}
|
|
@ -80,7 +80,9 @@ class ReadMarkerHelper @Inject constructor() {
|
|||
val newJumpToReadMarkerVisible = if (readMarkerId == null) {
|
||||
false
|
||||
} else {
|
||||
val positionOfReadMarker = timelineEventController.searchPositionOfEvent(readMarkerId)
|
||||
val correctedReadMarkerId = nonNullState.timeline?.getFirstDisplayableEventId(readMarkerId)
|
||||
?: readMarkerId
|
||||
val positionOfReadMarker = timelineEventController.searchPositionOfEvent(correctedReadMarkerId)
|
||||
if (positionOfReadMarker == null) {
|
||||
nonNullState.timeline?.isLive == true && lastVisibleItem > 0
|
||||
} else {
|
||||
|
|
|
@ -250,6 +250,7 @@ class RoomDetailFragment :
|
|||
if (scrollPosition == null) {
|
||||
scrollOnHighlightedEventCallback.scheduleScrollTo(it)
|
||||
} else {
|
||||
recyclerView.stopScroll()
|
||||
layoutManager.scrollToPosition(scrollPosition)
|
||||
}
|
||||
}
|
||||
|
@ -445,7 +446,7 @@ class RoomDetailFragment :
|
|||
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true)
|
||||
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
||||
scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager, timelineEventController)
|
||||
scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(layoutManager, timelineEventController)
|
||||
scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(recyclerView, layoutManager, timelineEventController)
|
||||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.itemAnimator = null
|
||||
recyclerView.setHasFixedSize(true)
|
||||
|
@ -958,7 +959,7 @@ class RoomDetailFragment :
|
|||
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
|
||||
val firstVisibleItem = timelineEventController.adapter.getModelAtPosition(firstVisibleItemPosition)
|
||||
val nextReadMarkerId = when (firstVisibleItem) {
|
||||
is BaseEventItem -> firstVisibleItem.getEventId()
|
||||
is BaseEventItem -> firstVisibleItem.getEventIds().firstOrNull()
|
||||
else -> null
|
||||
}
|
||||
if (nextReadMarkerId != null) {
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
package im.vector.riotx.features.home.room.detail
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.riotx.core.platform.DefaultListUpdateCallback
|
||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||
import kotlinx.android.synthetic.main.fragment_room_detail.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -29,7 +31,8 @@ import java.util.concurrent.atomic.AtomicReference
|
|||
/**
|
||||
* This handles scrolling to an event which wasn't yet loaded when scheduled.
|
||||
*/
|
||||
class ScrollOnHighlightedEventCallback(private val layoutManager: LinearLayoutManager,
|
||||
class ScrollOnHighlightedEventCallback(private val recyclerView: RecyclerView,
|
||||
private val layoutManager: LinearLayoutManager,
|
||||
private val timelineEventController: TimelineEventController) : DefaultListUpdateCallback {
|
||||
|
||||
private val scheduledEventId = AtomicReference<String?>()
|
||||
|
@ -56,6 +59,7 @@ class ScrollOnHighlightedEventCallback(private val layoutManager: LinearLayoutMa
|
|||
// Do not scroll it item is already visible
|
||||
if (positionToScroll !in firstVisibleItem..lastVisibleItem) {
|
||||
Timber.v("Scroll to $positionToScroll")
|
||||
recyclerView.stopScroll()
|
||||
layoutManager.scrollToPosition(positionToScroll)
|
||||
}
|
||||
scheduledEventId.set(null)
|
||||
|
|
|
@ -50,7 +50,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
private val mergedHeaderItemFactory: MergedHeaderItemFactory,
|
||||
@TimelineEventControllerHandler
|
||||
private val backgroundHandler: Handler
|
||||
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener {
|
||||
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor {
|
||||
|
||||
interface Callback : BaseCallback, ReactionPillCallback, AvatarCallback, UrlClickCallback, ReadReceiptsCallback {
|
||||
fun onLoadMore(direction: Timeline.Direction)
|
||||
|
@ -91,6 +91,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
}
|
||||
|
||||
private var showingForwardLoader = false
|
||||
// Map eventId to adapter position
|
||||
private val adapterPositionMapping = HashMap<String, Int>()
|
||||
private val modelCache = arrayListOf<CacheItemData?>()
|
||||
private var currentSnapshot: List<TimelineEvent> = emptyList()
|
||||
private var inSubmitList: Boolean = false
|
||||
|
@ -98,6 +100,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
|
||||
var callback: Callback? = null
|
||||
|
||||
|
||||
private val listUpdateCallback = object : ListUpdateCallback {
|
||||
|
||||
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
||||
|
@ -141,9 +144,22 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
}
|
||||
|
||||
init {
|
||||
addInterceptor(this)
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
// Update position when we are building new items
|
||||
override fun intercept(models: MutableList<EpoxyModel<*>>) {
|
||||
adapterPositionMapping.clear()
|
||||
models.forEachIndexed { index, epoxyModel ->
|
||||
if (epoxyModel is BaseEventItem) {
|
||||
epoxyModel.getEventIds().forEach {
|
||||
adapterPositionMapping[it] = index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun update(viewState: RoomDetailViewState, readMarkerVisible: Boolean) {
|
||||
if (timeline != viewState.timeline) {
|
||||
timeline = viewState.timeline
|
||||
|
@ -186,6 +202,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
timelineMediaSizeProvider.recyclerView = recyclerView
|
||||
}
|
||||
|
||||
|
||||
override fun buildModels() {
|
||||
val timestamp = System.currentTimeMillis()
|
||||
showingForwardLoader = LoadingItem_()
|
||||
|
@ -314,30 +331,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
}
|
||||
|
||||
fun searchPositionOfEvent(eventId: String?): Int? = synchronized(modelCache) {
|
||||
// Search in the cache
|
||||
if (eventId == null) {
|
||||
return null
|
||||
}
|
||||
var realPosition = 0
|
||||
if (showingForwardLoader) {
|
||||
realPosition++
|
||||
}
|
||||
for (i in 0 until modelCache.size) {
|
||||
val itemCache = modelCache[i] ?: continue
|
||||
if (itemCache.eventId == eventId) {
|
||||
return realPosition
|
||||
}
|
||||
if (itemCache.eventModel != null && !mergedHeaderItemFactory.isCollapsed(itemCache.localId)) {
|
||||
realPosition++
|
||||
}
|
||||
if (itemCache.mergedHeaderModel != null) {
|
||||
realPosition++
|
||||
}
|
||||
if (itemCache.formattedDayModel != null) {
|
||||
realPosition++
|
||||
}
|
||||
}
|
||||
return null
|
||||
return adapterPositionMapping[eventId]
|
||||
}
|
||||
|
||||
fun isLoadingForward() = showingForwardLoader
|
||||
|
|
|
@ -29,7 +29,6 @@ import androidx.core.view.children
|
|||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.VisibilityState
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
|
@ -158,8 +157,8 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
|||
return true
|
||||
}
|
||||
|
||||
override fun getEventId(): String? {
|
||||
return attributes.informationData.eventId
|
||||
override fun getEventIds(): List<String> {
|
||||
return listOf(attributes.informationData.eventId)
|
||||
}
|
||||
|
||||
protected open fun renderSendState(root: View, textView: TextView?, failureIndicator: ImageView? = null) {
|
||||
|
|
|
@ -44,12 +44,15 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
|
|||
|
||||
override fun bind(holder: H) {
|
||||
super.bind(holder)
|
||||
holder
|
||||
holder.leftGuideline.setGuidelineBegin(leftGuideline)
|
||||
holder.checkableBackground.isChecked = highlighted
|
||||
}
|
||||
|
||||
abstract fun getEventId(): String?
|
||||
/**
|
||||
* Returns the eventIds associated with the EventItem.
|
||||
* Will generally get only one, but it handles the merging items.
|
||||
*/
|
||||
abstract fun getEventIds(): List<String>
|
||||
|
||||
abstract class BaseHolder(@IdRes val stubId: Int) : VectorEpoxyHolder() {
|
||||
val leftGuideline by bind<Guideline>(R.id.messageStartGuideline)
|
||||
|
|
|
@ -55,7 +55,7 @@ abstract class DefaultItem : BaseEventItem<DefaultItem.Holder>() {
|
|||
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener)
|
||||
}
|
||||
|
||||
override fun getEventId(): String? {
|
||||
override fun getEventIds(): List<String> {
|
||||
return informationData.eventId
|
||||
}
|
||||
|
||||
|
|
|
@ -90,8 +90,8 @@ abstract class MergedHeaderItem : BaseEventItem<MergedHeaderItem.Holder>() {
|
|||
}
|
||||
|
||||
|
||||
override fun getEventId(): String? {
|
||||
return attributes.mergeData.firstOrNull()?.eventId
|
||||
override fun getEventIds(): List<String> {
|
||||
return attributes.mergeData.map { it.eventId }
|
||||
}
|
||||
|
||||
data class Data(
|
||||
|
|
|
@ -70,8 +70,8 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
|||
}
|
||||
|
||||
|
||||
override fun getEventId(): String? {
|
||||
return attributes.informationData.eventId
|
||||
override fun getEventIds(): List<String> {
|
||||
return listOf(attributes.informationData.eventId)
|
||||
}
|
||||
|
||||
override fun getViewType() = STUB_ID
|
||||
|
|
Loading…
Reference in New Issue