2019-01-18 10:12:08 +00:00
|
|
|
/*
|
2019-01-25 13:04:59 +00:00
|
|
|
* Copyright 2019 New Vector Ltd
|
2019-01-18 10:12:08 +00:00
|
|
|
*
|
2019-01-25 13:04:59 +00:00
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
2019-01-18 10:12:08 +00:00
|
|
|
*
|
2019-01-25 13:04:59 +00:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
2019-01-18 10:12:08 +00:00
|
|
|
*/
|
|
|
|
|
2019-07-02 15:27:08 +00:00
|
|
|
package im.vector.riotx.features.home.room.detail.timeline
|
2018-10-19 13:30:40 +00:00
|
|
|
|
2019-03-19 18:45:32 +00:00
|
|
|
import android.os.Handler
|
2019-03-21 19:21:45 +00:00
|
|
|
import android.os.Looper
|
2019-03-12 15:54:33 +00:00
|
|
|
import android.view.View
|
2019-03-15 18:27:56 +00:00
|
|
|
import androidx.recyclerview.widget.DiffUtil
|
|
|
|
import androidx.recyclerview.widget.ListUpdateCallback
|
2019-01-24 17:04:55 +00:00
|
|
|
import androidx.recyclerview.widget.RecyclerView
|
2019-03-15 18:27:56 +00:00
|
|
|
import com.airbnb.epoxy.EpoxyController
|
2019-01-10 10:37:14 +00:00
|
|
|
import com.airbnb.epoxy.EpoxyModel
|
2019-06-26 13:22:44 +00:00
|
|
|
import im.vector.matrix.android.api.session.room.model.message.*
|
2019-03-15 18:27:56 +00:00
|
|
|
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
2019-01-30 17:39:54 +00:00
|
|
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
2019-08-08 17:59:20 +00:00
|
|
|
import im.vector.riotx.core.date.VectorDateFormatter
|
2019-07-02 15:27:08 +00:00
|
|
|
import im.vector.riotx.core.epoxy.LoadingItem_
|
|
|
|
import im.vector.riotx.core.extensions.localDateTime
|
|
|
|
import im.vector.riotx.core.resources.UserPreferencesProvider
|
|
|
|
import im.vector.riotx.features.home.AvatarRenderer
|
|
|
|
import im.vector.riotx.features.home.room.detail.timeline.factory.TimelineItemFactory
|
|
|
|
import im.vector.riotx.features.home.room.detail.timeline.helper.*
|
|
|
|
import im.vector.riotx.features.home.room.detail.timeline.item.DaySeparatorItem
|
|
|
|
import im.vector.riotx.features.home.room.detail.timeline.item.DaySeparatorItem_
|
|
|
|
import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem
|
|
|
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
2019-08-12 15:59:07 +00:00
|
|
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
2019-07-02 15:27:08 +00:00
|
|
|
import im.vector.riotx.features.media.ImageContentRenderer
|
|
|
|
import im.vector.riotx.features.media.VideoContentRenderer
|
2019-04-18 17:09:15 +00:00
|
|
|
import org.threeten.bp.LocalDateTime
|
2019-06-18 18:00:20 +00:00
|
|
|
import javax.inject.Inject
|
2018-10-19 13:30:40 +00:00
|
|
|
|
2019-08-08 17:59:20 +00:00
|
|
|
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter,
|
2019-06-18 18:00:20 +00:00
|
|
|
private val timelineItemFactory: TimelineItemFactory,
|
|
|
|
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
2019-06-26 13:22:44 +00:00
|
|
|
private val avatarRenderer: AvatarRenderer,
|
|
|
|
@TimelineEventControllerHandler
|
2019-08-20 17:12:22 +00:00
|
|
|
private val backgroundHandler: Handler
|
2019-03-19 18:45:32 +00:00
|
|
|
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener {
|
2019-03-15 18:27:56 +00:00
|
|
|
|
2019-08-08 17:59:20 +00:00
|
|
|
interface Callback : BaseCallback, ReactionPillCallback, AvatarCallback, UrlClickCallback, ReadReceiptsCallback {
|
2019-08-20 17:12:22 +00:00
|
|
|
fun onEventInvisible(event: TimelineEvent)
|
2019-04-18 17:09:15 +00:00
|
|
|
fun onEventVisible(event: TimelineEvent)
|
2019-07-26 12:51:14 +00:00
|
|
|
fun onRoomCreateLinkClicked(url: String)
|
2019-06-18 10:45:24 +00:00
|
|
|
fun onEncryptedMessageClicked(informationData: MessageInformationData, view: View)
|
2019-04-11 17:19:52 +00:00
|
|
|
fun onImageMessageClicked(messageImageContent: MessageImageContent, mediaData: ImageContentRenderer.Data, view: View)
|
2019-04-12 11:46:59 +00:00
|
|
|
fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View)
|
2019-07-08 17:06:17 +00:00
|
|
|
fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent)
|
2019-04-12 10:38:02 +00:00
|
|
|
fun onAudioMessageClicked(messageAudioContent: MessageAudioContent)
|
2019-08-07 10:57:11 +00:00
|
|
|
fun onEditedDecorationClicked(informationData: MessageInformationData)
|
2019-03-21 19:21:45 +00:00
|
|
|
}
|
|
|
|
|
2019-05-16 16:33:32 +00:00
|
|
|
interface ReactionPillCallback {
|
|
|
|
fun onClickOnReactionPill(informationData: MessageInformationData, reaction: String, on: Boolean)
|
2019-06-05 17:23:57 +00:00
|
|
|
fun onLongClickOnReactionPill(informationData: MessageInformationData, reaction: String)
|
2019-05-16 16:33:32 +00:00
|
|
|
}
|
2019-06-05 21:45:46 +00:00
|
|
|
|
2019-06-07 08:01:42 +00:00
|
|
|
interface BaseCallback {
|
|
|
|
fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View)
|
|
|
|
fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean
|
|
|
|
}
|
|
|
|
|
2019-06-05 21:45:46 +00:00
|
|
|
interface AvatarCallback {
|
|
|
|
fun onAvatarClicked(informationData: MessageInformationData)
|
|
|
|
fun onMemberNameClicked(informationData: MessageInformationData)
|
2019-03-21 19:21:45 +00:00
|
|
|
}
|
|
|
|
|
2019-08-08 17:59:20 +00:00
|
|
|
interface ReadReceiptsCallback {
|
2019-08-12 15:59:07 +00:00
|
|
|
fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>)
|
2019-08-20 17:12:22 +00:00
|
|
|
fun onReadMarkerLongDisplayed(informationData: MessageInformationData)
|
2019-08-08 17:59:20 +00:00
|
|
|
}
|
|
|
|
|
2019-06-20 14:27:43 +00:00
|
|
|
interface UrlClickCallback {
|
|
|
|
fun onUrlClicked(url: String): Boolean
|
|
|
|
fun onUrlLongClicked(url: String): Boolean
|
|
|
|
}
|
|
|
|
|
2019-07-10 17:14:06 +00:00
|
|
|
private val collapsedEventIds = linkedSetOf<Long>()
|
2019-07-15 12:08:51 +00:00
|
|
|
private val mergeItemCollapseStates = HashMap<Long, Boolean>()
|
2019-04-17 20:37:25 +00:00
|
|
|
private val modelCache = arrayListOf<CacheItemData?>()
|
2019-04-18 17:09:15 +00:00
|
|
|
|
2019-03-15 18:27:56 +00:00
|
|
|
private var currentSnapshot: List<TimelineEvent> = emptyList()
|
2019-03-21 19:21:45 +00:00
|
|
|
private var inSubmitList: Boolean = false
|
|
|
|
private var timeline: Timeline? = null
|
|
|
|
|
|
|
|
var callback: Callback? = null
|
2019-03-15 18:27:56 +00:00
|
|
|
|
|
|
|
private val listUpdateCallback = object : ListUpdateCallback {
|
2019-03-19 18:45:32 +00:00
|
|
|
|
2019-03-15 18:27:56 +00:00
|
|
|
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
2019-06-20 15:03:14 +00:00
|
|
|
synchronized(modelCache) {
|
|
|
|
assertUpdateCallbacksAllowed()
|
|
|
|
(position until (position + count)).forEach {
|
|
|
|
modelCache[it] = null
|
|
|
|
}
|
|
|
|
requestModelBuild()
|
2019-03-15 18:27:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onMoved(fromPosition: Int, toPosition: Int) {
|
2019-06-20 15:03:14 +00:00
|
|
|
synchronized(modelCache) {
|
|
|
|
assertUpdateCallbacksAllowed()
|
|
|
|
val model = modelCache.removeAt(fromPosition)
|
|
|
|
modelCache.add(toPosition, model)
|
|
|
|
requestModelBuild()
|
|
|
|
}
|
2019-03-15 18:27:56 +00:00
|
|
|
}
|
|
|
|
|
2019-03-21 19:21:45 +00:00
|
|
|
override fun onInserted(position: Int, count: Int) {
|
2019-06-20 15:03:14 +00:00
|
|
|
synchronized(modelCache) {
|
|
|
|
assertUpdateCallbacksAllowed()
|
|
|
|
(0 until count).forEach {
|
|
|
|
modelCache.add(position, null)
|
|
|
|
}
|
|
|
|
requestModelBuild()
|
2019-03-15 18:27:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onRemoved(position: Int, count: Int) {
|
2019-06-20 15:03:14 +00:00
|
|
|
synchronized(modelCache) {
|
|
|
|
assertUpdateCallbacksAllowed()
|
|
|
|
(0 until count).forEach {
|
|
|
|
modelCache.removeAt(position)
|
|
|
|
}
|
|
|
|
requestModelBuild()
|
2019-03-21 19:21:45 +00:00
|
|
|
}
|
2019-03-15 18:27:56 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-27 21:42:46 +00:00
|
|
|
|
2019-03-20 17:24:17 +00:00
|
|
|
init {
|
|
|
|
requestModelBuild()
|
|
|
|
}
|
|
|
|
|
2019-06-20 14:27:43 +00:00
|
|
|
fun setTimeline(timeline: Timeline?, eventIdToHighlight: String?) {
|
2019-03-15 18:27:56 +00:00
|
|
|
if (this.timeline != timeline) {
|
|
|
|
this.timeline = timeline
|
|
|
|
this.timeline?.listener = this
|
2019-06-20 14:27:43 +00:00
|
|
|
|
|
|
|
// Clear cache
|
2019-06-20 15:03:14 +00:00
|
|
|
synchronized(modelCache) {
|
|
|
|
for (i in 0 until modelCache.size) {
|
|
|
|
modelCache[i] = null
|
|
|
|
}
|
2019-06-20 14:27:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.eventIdToHighlight != eventIdToHighlight) {
|
|
|
|
// Clear cache to force a refresh
|
2019-06-20 15:03:14 +00:00
|
|
|
synchronized(modelCache) {
|
|
|
|
for (i in 0 until modelCache.size) {
|
|
|
|
if (modelCache[i]?.eventId == eventIdToHighlight
|
2019-08-08 17:59:20 +00:00
|
|
|
|| modelCache[i]?.eventId == this.eventIdToHighlight) {
|
2019-06-20 15:03:14 +00:00
|
|
|
modelCache[i] = null
|
|
|
|
}
|
2019-06-20 14:42:22 +00:00
|
|
|
}
|
2019-06-20 14:27:43 +00:00
|
|
|
}
|
|
|
|
this.eventIdToHighlight = eventIdToHighlight
|
|
|
|
|
|
|
|
requestModelBuild()
|
2019-01-11 14:17:15 +00:00
|
|
|
}
|
2019-01-07 18:38:36 +00:00
|
|
|
}
|
|
|
|
|
2019-06-20 14:27:43 +00:00
|
|
|
private var eventIdToHighlight: String? = null
|
|
|
|
|
2019-01-24 17:04:55 +00:00
|
|
|
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
|
|
|
super.onAttachedToRecyclerView(recyclerView)
|
|
|
|
timelineMediaSizeProvider.recyclerView = recyclerView
|
|
|
|
}
|
2018-10-19 13:30:40 +00:00
|
|
|
|
2019-03-15 18:27:56 +00:00
|
|
|
override fun buildModels() {
|
2019-07-15 12:08:51 +00:00
|
|
|
val loaderAdded = LoadingItem_()
|
2019-03-19 18:45:32 +00:00
|
|
|
.id("forward_loading_item")
|
|
|
|
.addWhen(Timeline.Direction.FORWARDS)
|
|
|
|
|
2019-03-21 19:21:45 +00:00
|
|
|
val timelineModels = getModels()
|
|
|
|
add(timelineModels)
|
2019-03-19 18:45:32 +00:00
|
|
|
|
2019-07-15 12:08:51 +00:00
|
|
|
// Avoid displaying two loaders if there is no elements between them
|
|
|
|
if (!loaderAdded || timelineModels.isNotEmpty()) {
|
|
|
|
LoadingItem_()
|
|
|
|
.id("backward_loading_item")
|
|
|
|
.addWhen(Timeline.Direction.BACKWARDS)
|
|
|
|
}
|
2019-03-15 18:27:56 +00:00
|
|
|
}
|
|
|
|
|
2019-03-21 19:21:45 +00:00
|
|
|
// Timeline.LISTENER ***************************************************************************
|
|
|
|
|
|
|
|
override fun onUpdated(snapshot: List<TimelineEvent>) {
|
|
|
|
submitSnapshot(snapshot)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun submitSnapshot(newSnapshot: List<TimelineEvent>) {
|
|
|
|
backgroundHandler.post {
|
|
|
|
inSubmitList = true
|
|
|
|
val diffCallback = TimelineEventDiffUtilCallback(currentSnapshot, newSnapshot)
|
|
|
|
currentSnapshot = newSnapshot
|
|
|
|
val diffResult = DiffUtil.calculateDiff(diffCallback)
|
|
|
|
diffResult.dispatchUpdatesTo(listUpdateCallback)
|
|
|
|
inSubmitList = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun assertUpdateCallbacksAllowed() {
|
|
|
|
require(inSubmitList || Looper.myLooper() == backgroundHandler.looper)
|
2019-03-19 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
2019-03-15 18:27:56 +00:00
|
|
|
private fun getModels(): List<EpoxyModel<*>> {
|
2019-06-20 15:03:14 +00:00
|
|
|
synchronized(modelCache) {
|
|
|
|
(0 until modelCache.size).forEach { position ->
|
|
|
|
// Should be build if not cached or if cached but contains mergedHeader or formattedDay
|
|
|
|
// We then are sure we always have items up to date.
|
|
|
|
if (modelCache[position] == null
|
2019-08-08 17:59:20 +00:00
|
|
|
|| modelCache[position]?.mergedHeaderModel != null
|
|
|
|
|| modelCache[position]?.formattedDayModel != null) {
|
2019-06-20 15:03:14 +00:00
|
|
|
modelCache[position] = buildItemModels(position, currentSnapshot)
|
|
|
|
}
|
2019-03-15 18:27:56 +00:00
|
|
|
}
|
2019-06-20 15:03:14 +00:00
|
|
|
return modelCache
|
|
|
|
.map {
|
|
|
|
val eventModel = if (it == null || collapsedEventIds.contains(it.localId)) {
|
|
|
|
null
|
|
|
|
} else {
|
|
|
|
it.eventModel
|
|
|
|
}
|
|
|
|
listOf(eventModel, it?.mergedHeaderModel, it?.formattedDayModel)
|
2019-04-18 17:09:15 +00:00
|
|
|
}
|
2019-06-20 15:03:14 +00:00
|
|
|
.flatten()
|
|
|
|
.filterNotNull()
|
|
|
|
}
|
2019-04-17 20:37:25 +00:00
|
|
|
}
|
|
|
|
|
2019-03-15 18:27:56 +00:00
|
|
|
|
2019-04-17 20:37:25 +00:00
|
|
|
private fun buildItemModels(currentPosition: Int, items: List<TimelineEvent>): CacheItemData {
|
2019-03-15 18:27:56 +00:00
|
|
|
val event = items[currentPosition]
|
2019-08-20 17:12:22 +00:00
|
|
|
val nextEvent = items.nextOrNull(currentPosition)
|
2019-01-10 10:37:14 +00:00
|
|
|
val date = event.root.localDateTime()
|
2019-04-18 17:09:15 +00:00
|
|
|
val nextDate = nextEvent?.root?.localDateTime()
|
2019-01-10 10:37:14 +00:00
|
|
|
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
|
|
|
|
|
2019-06-20 14:27:43 +00:00
|
|
|
val eventModel = timelineItemFactory.create(event, nextEvent, eventIdToHighlight, callback).also {
|
2019-04-18 17:09:15 +00:00
|
|
|
it.id(event.localId)
|
|
|
|
it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event))
|
2019-01-10 10:37:14 +00:00
|
|
|
}
|
2019-04-18 17:09:15 +00:00
|
|
|
val mergedHeaderModel = buildMergedHeaderItem(event, nextEvent, items, addDaySeparator, currentPosition)
|
|
|
|
val daySeparatorItem = buildDaySeparatorItem(addDaySeparator, date)
|
|
|
|
|
2019-06-20 14:27:43 +00:00
|
|
|
return CacheItemData(event.localId, event.root.eventId, eventModel, mergedHeaderModel, daySeparatorItem)
|
2019-04-18 17:09:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun buildDaySeparatorItem(addDaySeparator: Boolean, date: LocalDateTime): DaySeparatorItem? {
|
|
|
|
return if (addDaySeparator) {
|
2019-01-10 10:37:14 +00:00
|
|
|
val formattedDay = dateFormatter.formatMessageDay(date)
|
2019-04-17 20:37:25 +00:00
|
|
|
DaySeparatorItem_().formattedDay(formattedDay).id(formattedDay)
|
|
|
|
} else {
|
|
|
|
null
|
2019-01-10 10:37:14 +00:00
|
|
|
}
|
2019-04-18 17:09:15 +00:00
|
|
|
}
|
|
|
|
|
2019-06-20 14:27:43 +00:00
|
|
|
// TODO Phase 3 Handle the case where the eventId we have to highlight is merged
|
2019-04-18 17:09:15 +00:00
|
|
|
private fun buildMergedHeaderItem(event: TimelineEvent,
|
|
|
|
nextEvent: TimelineEvent?,
|
|
|
|
items: List<TimelineEvent>,
|
|
|
|
addDaySeparator: Boolean,
|
|
|
|
currentPosition: Int): MergedHeaderItem? {
|
2019-05-26 17:21:45 +00:00
|
|
|
return if (!event.canBeMerged() || (nextEvent?.root?.getClearType() == event.root.getClearType() && !addDaySeparator)) {
|
2019-04-18 17:09:15 +00:00
|
|
|
null
|
|
|
|
} else {
|
|
|
|
val prevSameTypeEvents = items.prevSameTypeEvents(currentPosition, 2)
|
|
|
|
if (prevSameTypeEvents.isEmpty()) {
|
|
|
|
null
|
|
|
|
} else {
|
2019-04-30 17:55:55 +00:00
|
|
|
val mergedEvents = (prevSameTypeEvents + listOf(event)).asReversed()
|
|
|
|
val mergedData = mergedEvents.map { mergedEvent ->
|
2019-05-27 13:43:26 +00:00
|
|
|
val senderAvatar = mergedEvent.senderAvatar()
|
|
|
|
val senderName = mergedEvent.senderName()
|
2019-04-18 17:09:15 +00:00
|
|
|
MergedHeaderItem.Data(
|
2019-06-18 13:58:19 +00:00
|
|
|
userId = mergedEvent.root.senderId ?: "",
|
2019-04-30 17:55:55 +00:00
|
|
|
avatarUrl = senderAvatar,
|
|
|
|
memberName = senderName ?: "",
|
|
|
|
eventId = mergedEvent.localId
|
2019-04-18 17:09:15 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
val mergedEventIds = mergedEvents.map { it.localId }
|
2019-04-30 17:55:55 +00:00
|
|
|
// We try to find if one of the item id were used as mergeItemCollapseStates key
|
|
|
|
// => handle case where paginating from mergeable events and we get more
|
|
|
|
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
|
2019-08-08 17:59:20 +00:00
|
|
|
val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey)
|
|
|
|
?: true
|
2019-04-30 17:55:55 +00:00
|
|
|
val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState }
|
2019-04-18 17:09:15 +00:00
|
|
|
if (isCollapsed) {
|
|
|
|
collapsedEventIds.addAll(mergedEventIds)
|
|
|
|
} else {
|
|
|
|
collapsedEventIds.removeAll(mergedEventIds)
|
|
|
|
}
|
2019-07-10 17:14:06 +00:00
|
|
|
val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() }
|
2019-07-02 19:17:41 +00:00
|
|
|
MergedHeaderItem(isCollapsed, mergeId, mergedData, avatarRenderer) {
|
2019-04-18 17:09:15 +00:00
|
|
|
mergeItemCollapseStates[event.localId] = it
|
|
|
|
requestModelBuild()
|
2019-07-02 19:17:41 +00:00
|
|
|
}.also {
|
2019-07-02 10:08:44 +00:00
|
|
|
it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents))
|
2019-04-18 17:09:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-01-10 10:37:14 +00:00
|
|
|
}
|
|
|
|
|
2019-07-15 12:08:51 +00:00
|
|
|
/**
|
|
|
|
* Return true if added
|
|
|
|
*/
|
|
|
|
private fun LoadingItem_.addWhen(direction: Timeline.Direction): Boolean {
|
2019-04-17 20:37:25 +00:00
|
|
|
val shouldAdd = timeline?.hasMoreToLoad(direction) ?: false
|
2019-03-21 19:21:45 +00:00
|
|
|
addIf(shouldAdd, this@TimelineEventController)
|
2019-07-15 12:08:51 +00:00
|
|
|
return shouldAdd
|
2018-12-19 10:50:44 +00:00
|
|
|
}
|
|
|
|
|
2019-08-20 17:12:22 +00:00
|
|
|
fun searchPositionOfEvent(eventId: String): Int? = synchronized(modelCache) {
|
|
|
|
// Search in the cache
|
|
|
|
var realPosition = 0
|
|
|
|
for (i in 0 until modelCache.size) {
|
|
|
|
val itemCache = modelCache[i]
|
|
|
|
if (itemCache?.eventId == eventId) {
|
|
|
|
return realPosition
|
|
|
|
}
|
|
|
|
if (itemCache?.eventModel != null) {
|
|
|
|
realPosition++
|
2019-06-20 14:27:43 +00:00
|
|
|
}
|
2019-08-20 17:12:22 +00:00
|
|
|
if (itemCache?.mergedHeaderModel != null) {
|
|
|
|
realPosition++
|
|
|
|
}
|
|
|
|
if (itemCache?.formattedDayModel != null) {
|
|
|
|
realPosition++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
2019-06-20 14:27:43 +00:00
|
|
|
|
2019-08-20 17:12:22 +00:00
|
|
|
fun searchEventIdAtPosition(position: Int): String? = synchronized(modelCache) {
|
|
|
|
var offsetValue = 0
|
|
|
|
for (i in 0 until position) {
|
|
|
|
val itemCache = modelCache[i]
|
|
|
|
if (itemCache?.eventModel == null) {
|
|
|
|
offsetValue--
|
|
|
|
}
|
|
|
|
if (itemCache?.mergedHeaderModel != null) {
|
|
|
|
offsetValue++
|
|
|
|
}
|
|
|
|
if (itemCache?.formattedDayModel != null) {
|
|
|
|
offsetValue++
|
|
|
|
}
|
2019-06-20 15:03:14 +00:00
|
|
|
}
|
2019-08-20 17:12:22 +00:00
|
|
|
return modelCache.getOrNull(position - offsetValue)?.eventId
|
2019-06-20 14:27:43 +00:00
|
|
|
}
|
2019-08-20 17:12:22 +00:00
|
|
|
|
|
|
|
private data class CacheItemData(
|
|
|
|
val localId: Long,
|
|
|
|
val eventId: String?,
|
|
|
|
val eventModel: EpoxyModel<*>? = null,
|
|
|
|
val mergedHeaderModel: MergedHeaderItem? = null,
|
|
|
|
val formattedDayModel: DaySeparatorItem? = null
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|