mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +03:00
Timeline : remove filtering from SDK
This commit is contained in:
parent
3e8370cdc7
commit
d6d4293ea8
12 changed files with 51 additions and 441 deletions
|
@ -95,12 +95,6 @@ interface Timeline {
|
||||||
*/
|
*/
|
||||||
fun getTimelineEventWithId(eventId: String?): TimelineEvent?
|
fun getTimelineEventWithId(eventId: String?): TimelineEvent?
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the first displayable events starting from eventId.
|
|
||||||
* It does depend on the provided [TimelineSettings].
|
|
||||||
*/
|
|
||||||
fun getFirstDisplayableEventId(eventId: String): String?
|
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
/**
|
/**
|
||||||
* Call when the timeline has been updated through pagination or sync.
|
* Call when the timeline has been updated through pagination or sync.
|
||||||
|
|
|
@ -24,10 +24,6 @@ data class TimelineSettings(
|
||||||
* The initial number of events to retrieve from cache. You might get less events if you don't have loaded enough yet.
|
* The initial number of events to retrieve from cache. You might get less events if you don't have loaded enough yet.
|
||||||
*/
|
*/
|
||||||
val initialSize: Int,
|
val initialSize: Int,
|
||||||
/**
|
|
||||||
* Filters for timeline event
|
|
||||||
*/
|
|
||||||
val filters: TimelineEventFilters = TimelineEventFilters(),
|
|
||||||
/**
|
/**
|
||||||
* If true, will build read receipts for each event.
|
* If true, will build read receipts for each event.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -25,9 +25,9 @@ import javax.inject.Inject
|
||||||
|
|
||||||
internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper) {
|
internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper) {
|
||||||
|
|
||||||
fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true, correctedReadReceipts: List<ReadReceipt>? = null): TimelineEvent {
|
fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true): TimelineEvent {
|
||||||
val readReceipts = if (buildReadReceipts) {
|
val readReceipts = if (buildReadReceipts) {
|
||||||
correctedReadReceipts ?: timelineEventEntity.readReceipts
|
timelineEventEntity.readReceipts
|
||||||
?.let {
|
?.let {
|
||||||
readReceiptsSummaryMapper.map(it)
|
readReceiptsSummaryMapper.map(it)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
@ -70,14 +69,12 @@ internal class DefaultTimeline(
|
||||||
private val paginationTask: PaginationTask,
|
private val paginationTask: PaginationTask,
|
||||||
private val timelineEventMapper: TimelineEventMapper,
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
private val settings: TimelineSettings,
|
private val settings: TimelineSettings,
|
||||||
private val hiddenReadReceipts: TimelineHiddenReadReceipts,
|
|
||||||
private val timelineInput: TimelineInput,
|
private val timelineInput: TimelineInput,
|
||||||
private val eventDecryptor: TimelineEventDecryptor,
|
private val eventDecryptor: TimelineEventDecryptor,
|
||||||
private val realmSessionProvider: RealmSessionProvider,
|
private val realmSessionProvider: RealmSessionProvider,
|
||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
private val readReceiptHandler: ReadReceiptHandler
|
private val readReceiptHandler: ReadReceiptHandler
|
||||||
) : Timeline,
|
) : Timeline,
|
||||||
TimelineHiddenReadReceipts.Delegate,
|
|
||||||
TimelineInput.Listener,
|
TimelineInput.Listener,
|
||||||
UIEchoManager.Listener {
|
UIEchoManager.Listener {
|
||||||
|
|
||||||
|
@ -93,8 +90,7 @@ internal class DefaultTimeline(
|
||||||
private val cancelableBag = CancelableBag()
|
private val cancelableBag = CancelableBag()
|
||||||
private val debouncer = Debouncer(mainHandler)
|
private val debouncer = Debouncer(mainHandler)
|
||||||
|
|
||||||
private lateinit var nonFilteredEvents: RealmResults<TimelineEventEntity>
|
private lateinit var timelineEvents: RealmResults<TimelineEventEntity>
|
||||||
private lateinit var filteredEvents: RealmResults<TimelineEventEntity>
|
|
||||||
private lateinit var sendingEvents: RealmResults<TimelineEventEntity>
|
private lateinit var sendingEvents: RealmResults<TimelineEventEntity>
|
||||||
|
|
||||||
private var prevDisplayIndex: Int? = null
|
private var prevDisplayIndex: Int? = null
|
||||||
|
@ -168,16 +164,9 @@ internal class DefaultTimeline(
|
||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
|
timelineEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
|
||||||
filteredEvents = nonFilteredEvents.where()
|
timelineEvents.addChangeListener(eventsChangeListener)
|
||||||
.filterEventsWithSettings(settings)
|
|
||||||
.findAll()
|
|
||||||
nonFilteredEvents.addChangeListener(eventsChangeListener)
|
|
||||||
handleInitialLoad()
|
handleInitialLoad()
|
||||||
if (settings.shouldHandleHiddenReadReceipts()) {
|
|
||||||
hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
loadRoomMembersTask
|
loadRoomMembersTask
|
||||||
.configureWith(LoadRoomMembersTask.Params(roomId)) {
|
.configureWith(LoadRoomMembersTask.Params(roomId)) {
|
||||||
this.callback = NoOpMatrixCallback()
|
this.callback = NoOpMatrixCallback()
|
||||||
|
@ -205,10 +194,6 @@ internal class DefaultTimeline(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TimelineSettings.shouldHandleHiddenReadReceipts(): Boolean {
|
|
||||||
return buildReadReceipts && (filters.filterEdits || filters.filterTypes)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
if (isStarted.compareAndSet(true, false)) {
|
if (isStarted.compareAndSet(true, false)) {
|
||||||
isReady.set(false)
|
isReady.set(false)
|
||||||
|
@ -220,11 +205,8 @@ internal class DefaultTimeline(
|
||||||
if (this::sendingEvents.isInitialized) {
|
if (this::sendingEvents.isInitialized) {
|
||||||
sendingEvents.removeAllChangeListeners()
|
sendingEvents.removeAllChangeListeners()
|
||||||
}
|
}
|
||||||
if (this::nonFilteredEvents.isInitialized) {
|
if (this::timelineEvents.isInitialized) {
|
||||||
nonFilteredEvents.removeAllChangeListeners()
|
timelineEvents.removeAllChangeListeners()
|
||||||
}
|
|
||||||
if (settings.shouldHandleHiddenReadReceipts()) {
|
|
||||||
hiddenReadReceipts.dispose()
|
|
||||||
}
|
}
|
||||||
clearAllValues()
|
clearAllValues()
|
||||||
backgroundRealm.getAndSet(null).also {
|
backgroundRealm.getAndSet(null).also {
|
||||||
|
@ -256,48 +238,6 @@ internal class DefaultTimeline(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFirstDisplayableEventId(eventId: String): String? {
|
|
||||||
// If the item is built, the id is obviously displayable
|
|
||||||
val builtIndex = builtEventsIdMap[eventId]
|
|
||||||
if (builtIndex != null) {
|
|
||||||
return eventId
|
|
||||||
}
|
|
||||||
// Otherwise, we should check if the event is in the db, but is hidden because of filters
|
|
||||||
return realmSessionProvider.withRealm { localRealm ->
|
|
||||||
val nonFilteredEvents = buildEventQuery(localRealm)
|
|
||||||
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
|
||||||
.findAll()
|
|
||||||
|
|
||||||
val nonFilteredEvent = nonFilteredEvents.where()
|
|
||||||
.equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
|
|
||||||
.findFirst()
|
|
||||||
|
|
||||||
val filteredEvents = nonFilteredEvents.where()
|
|
||||||
.filterEventsWithSettings(settings)
|
|
||||||
.findAll()
|
|
||||||
val isEventInDb = nonFilteredEvent != null
|
|
||||||
|
|
||||||
val isHidden = isEventInDb && filteredEvents.where()
|
|
||||||
.equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
|
|
||||||
.findFirst() == null
|
|
||||||
|
|
||||||
if (isHidden) {
|
|
||||||
val displayIndex = nonFilteredEvent?.displayIndex
|
|
||||||
if (displayIndex != null) {
|
|
||||||
// Then we are looking for the first displayable event after the hidden one
|
|
||||||
val firstDisplayedEvent = filteredEvents.where()
|
|
||||||
.lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, displayIndex)
|
|
||||||
.findFirst()
|
|
||||||
firstDisplayedEvent?.eventId
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hasMoreToLoad(direction: Timeline.Direction): Boolean {
|
override fun hasMoreToLoad(direction: Timeline.Direction): Boolean {
|
||||||
return hasMoreInCache(direction) || !hasReachedEnd(direction)
|
return hasMoreInCache(direction) || !hasReachedEnd(direction)
|
||||||
}
|
}
|
||||||
|
@ -319,18 +259,6 @@ internal class DefaultTimeline(
|
||||||
listeners.clear()
|
listeners.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimelineHiddenReadReceipts.Delegate
|
|
||||||
|
|
||||||
override fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean {
|
|
||||||
return rebuildEvent(eventId) { te ->
|
|
||||||
te.copy(readReceipts = readReceipts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onReadReceiptsUpdated() {
|
|
||||||
postSnapshot()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNewTimelineEvents(roomId: String, eventIds: List<String>) {
|
override fun onNewTimelineEvents(roomId: String, eventIds: List<String>) {
|
||||||
if (isLive && this.roomId == roomId) {
|
if (isLive && this.roomId == roomId) {
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
|
@ -341,18 +269,13 @@ internal class DefaultTimeline(
|
||||||
|
|
||||||
override fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) {
|
override fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) {
|
||||||
if (roomId != this.roomId || !isLive) return
|
if (roomId != this.roomId || !isLive) return
|
||||||
|
uiEchoManager.onLocalEchoCreated(timelineEvent)
|
||||||
val postSnapShot = uiEchoManager.onLocalEchoCreated(timelineEvent)
|
listeners.forEach {
|
||||||
|
tryOrNull {
|
||||||
if (listOf(timelineEvent).filterEventsWithSettings(settings).isNotEmpty()) {
|
|
||||||
listeners.forEach {
|
|
||||||
it.onNewTimelineEvents(listOf(timelineEvent.eventId))
|
it.onNewTimelineEvents(listOf(timelineEvent.eventId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
postSnapshot()
|
||||||
if (postSnapShot) {
|
|
||||||
postSnapshot()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) {
|
override fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) {
|
||||||
|
@ -439,23 +362,21 @@ internal class DefaultTimeline(
|
||||||
val builtSendingEvents = mutableListOf<TimelineEvent>()
|
val builtSendingEvents = mutableListOf<TimelineEvent>()
|
||||||
if (hasReachedEnd(Timeline.Direction.FORWARDS) && !hasMoreInCache(Timeline.Direction.FORWARDS)) {
|
if (hasReachedEnd(Timeline.Direction.FORWARDS) && !hasMoreInCache(Timeline.Direction.FORWARDS)) {
|
||||||
uiEchoManager.getInMemorySendingEvents()
|
uiEchoManager.getInMemorySendingEvents()
|
||||||
.filterSendingEventsTo(builtSendingEvents)
|
.updateWithUiEchoInto(builtSendingEvents)
|
||||||
sendingEvents
|
sendingEvents
|
||||||
.filter { timelineEvent ->
|
.filter { timelineEvent ->
|
||||||
builtSendingEvents.none { it.eventId == timelineEvent.eventId }
|
builtSendingEvents.none { it.eventId == timelineEvent.eventId }
|
||||||
}
|
}
|
||||||
.map { timelineEventMapper.map(it) }
|
.map { timelineEventMapper.map(it) }
|
||||||
.filterSendingEventsTo(builtSendingEvents)
|
.updateWithUiEchoInto(builtSendingEvents)
|
||||||
}
|
}
|
||||||
return builtSendingEvents
|
return builtSendingEvents
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<TimelineEvent>.filterSendingEventsTo(target: MutableList<TimelineEvent>) {
|
private fun List<TimelineEvent>.updateWithUiEchoInto(target: MutableList<TimelineEvent>) {
|
||||||
target.addAll(
|
target.addAll(
|
||||||
// Filter out sending event that are not displayable!
|
// Get most up to date send state (in memory)
|
||||||
filterEventsWithSettings(settings)
|
map { uiEchoManager.updateSentStateWithUiEcho(it) }
|
||||||
// Get most up to date send state (in memory)
|
|
||||||
.map { uiEchoManager.updateSentStateWithUiEcho(it) }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,14 +386,14 @@ internal class DefaultTimeline(
|
||||||
|
|
||||||
private fun getState(direction: Timeline.Direction): TimelineState {
|
private fun getState(direction: Timeline.Direction): TimelineState {
|
||||||
return when (direction) {
|
return when (direction) {
|
||||||
Timeline.Direction.FORWARDS -> forwardsState.get()
|
Timeline.Direction.FORWARDS -> forwardsState.get()
|
||||||
Timeline.Direction.BACKWARDS -> backwardsState.get()
|
Timeline.Direction.BACKWARDS -> backwardsState.get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateState(direction: Timeline.Direction, update: (TimelineState) -> TimelineState) {
|
private fun updateState(direction: Timeline.Direction, update: (TimelineState) -> TimelineState) {
|
||||||
val stateReference = when (direction) {
|
val stateReference = when (direction) {
|
||||||
Timeline.Direction.FORWARDS -> forwardsState
|
Timeline.Direction.FORWARDS -> forwardsState
|
||||||
Timeline.Direction.BACKWARDS -> backwardsState
|
Timeline.Direction.BACKWARDS -> backwardsState
|
||||||
}
|
}
|
||||||
val currentValue = stateReference.get()
|
val currentValue = stateReference.get()
|
||||||
|
@ -487,9 +408,9 @@ internal class DefaultTimeline(
|
||||||
var shouldFetchInitialEvent = false
|
var shouldFetchInitialEvent = false
|
||||||
val currentInitialEventId = initialEventId
|
val currentInitialEventId = initialEventId
|
||||||
val initialDisplayIndex = if (currentInitialEventId == null) {
|
val initialDisplayIndex = if (currentInitialEventId == null) {
|
||||||
nonFilteredEvents.firstOrNull()?.displayIndex
|
timelineEvents.firstOrNull()?.displayIndex
|
||||||
} else {
|
} else {
|
||||||
val initialEvent = nonFilteredEvents.where()
|
val initialEvent = timelineEvents.where()
|
||||||
.equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId)
|
.equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
|
|
||||||
|
@ -501,7 +422,7 @@ internal class DefaultTimeline(
|
||||||
if (currentInitialEventId != null && shouldFetchInitialEvent) {
|
if (currentInitialEventId != null && shouldFetchInitialEvent) {
|
||||||
fetchEvent(currentInitialEventId)
|
fetchEvent(currentInitialEventId)
|
||||||
} else {
|
} else {
|
||||||
val count = filteredEvents.size.coerceAtMost(settings.initialSize)
|
val count = timelineEvents.size.coerceAtMost(settings.initialSize)
|
||||||
if (initialEventId == null) {
|
if (initialEventId == null) {
|
||||||
paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count)
|
paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count)
|
||||||
} else {
|
} else {
|
||||||
|
@ -541,8 +462,7 @@ internal class DefaultTimeline(
|
||||||
val eventEntity = results[index]
|
val eventEntity = results[index]
|
||||||
eventEntity?.eventId?.let { eventId ->
|
eventEntity?.eventId?.let { eventId ->
|
||||||
postSnapshot = rebuildEvent(eventId) {
|
postSnapshot = rebuildEvent(eventId) {
|
||||||
val builtEvent = buildTimelineEvent(eventEntity)
|
buildTimelineEvent(eventEntity)
|
||||||
listOf(builtEvent).filterEventsWithSettings(settings).firstOrNull()
|
|
||||||
} || postSnapshot
|
} || postSnapshot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -563,9 +483,9 @@ internal class DefaultTimeline(
|
||||||
// We are in the case where event exists, but we do not know the token.
|
// We are in the case where event exists, but we do not know the token.
|
||||||
// Fetch (again) the last event to get a token
|
// Fetch (again) the last event to get a token
|
||||||
val lastKnownEventId = if (direction == Timeline.Direction.FORWARDS) {
|
val lastKnownEventId = if (direction == Timeline.Direction.FORWARDS) {
|
||||||
nonFilteredEvents.firstOrNull()?.eventId
|
timelineEvents.firstOrNull()?.eventId
|
||||||
} else {
|
} else {
|
||||||
nonFilteredEvents.lastOrNull()?.eventId
|
timelineEvents.lastOrNull()?.eventId
|
||||||
}
|
}
|
||||||
if (lastKnownEventId == null) {
|
if (lastKnownEventId == null) {
|
||||||
updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
|
updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) }
|
||||||
|
@ -636,7 +556,7 @@ internal class DefaultTimeline(
|
||||||
* Return the current Chunk
|
* Return the current Chunk
|
||||||
*/
|
*/
|
||||||
private fun getLiveChunk(): ChunkEntity? {
|
private fun getLiveChunk(): ChunkEntity? {
|
||||||
return nonFilteredEvents.firstOrNull()?.chunk?.firstOrNull()
|
return timelineEvents.firstOrNull()?.chunk?.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -680,14 +600,13 @@ internal class DefaultTimeline(
|
||||||
val time = System.currentTimeMillis() - start
|
val time = System.currentTimeMillis() - start
|
||||||
Timber.v("Built ${offsetResults.size} items from db in $time ms")
|
Timber.v("Built ${offsetResults.size} items from db in $time ms")
|
||||||
// For the case where wo reach the lastForward chunk
|
// For the case where wo reach the lastForward chunk
|
||||||
updateLoadingStates(filteredEvents)
|
updateLoadingStates(timelineEvents)
|
||||||
return offsetResults.size
|
return offsetResults.size
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildTimelineEvent(eventEntity: TimelineEventEntity) = timelineEventMapper.map(
|
private fun buildTimelineEvent(eventEntity: TimelineEventEntity) = timelineEventMapper.map(
|
||||||
timelineEventEntity = eventEntity,
|
timelineEventEntity = eventEntity,
|
||||||
buildReadReceipts = settings.buildReadReceipts,
|
buildReadReceipts = settings.buildReadReceipts
|
||||||
correctedReadReceipts = hiddenReadReceipts.correctedReadReceipts(eventEntity.eventId)
|
|
||||||
).let {
|
).let {
|
||||||
// eventually enhance with ui echo?
|
// eventually enhance with ui echo?
|
||||||
(uiEchoManager.decorateEventWithReactionUiEcho(it) ?: it)
|
(uiEchoManager.decorateEventWithReactionUiEcho(it) ?: it)
|
||||||
|
@ -699,7 +618,7 @@ internal class DefaultTimeline(
|
||||||
private fun getOffsetResults(startDisplayIndex: Int,
|
private fun getOffsetResults(startDisplayIndex: Int,
|
||||||
direction: Timeline.Direction,
|
direction: Timeline.Direction,
|
||||||
count: Long): RealmResults<TimelineEventEntity> {
|
count: Long): RealmResults<TimelineEventEntity> {
|
||||||
val offsetQuery = filteredEvents.where()
|
val offsetQuery = timelineEvents.where()
|
||||||
if (direction == Timeline.Direction.BACKWARDS) {
|
if (direction == Timeline.Direction.BACKWARDS) {
|
||||||
offsetQuery
|
offsetQuery
|
||||||
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
|
@ -747,7 +666,7 @@ internal class DefaultTimeline(
|
||||||
if (isReady.get().not()) {
|
if (isReady.get().not()) {
|
||||||
return@post
|
return@post
|
||||||
}
|
}
|
||||||
updateLoadingStates(filteredEvents)
|
updateLoadingStates(timelineEvents)
|
||||||
val snapshot = createSnapshot()
|
val snapshot = createSnapshot()
|
||||||
val runnable = Runnable {
|
val runnable = Runnable {
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
|
@ -783,10 +702,10 @@ internal class DefaultTimeline(
|
||||||
return object : MatrixCallback<TokenChunkEventPersistor.Result> {
|
return object : MatrixCallback<TokenChunkEventPersistor.Result> {
|
||||||
override fun onSuccess(data: TokenChunkEventPersistor.Result) {
|
override fun onSuccess(data: TokenChunkEventPersistor.Result) {
|
||||||
when (data) {
|
when (data) {
|
||||||
TokenChunkEventPersistor.Result.SUCCESS -> {
|
TokenChunkEventPersistor.Result.SUCCESS -> {
|
||||||
Timber.v("Success fetching $limit items $direction from pagination request")
|
Timber.v("Success fetching $limit items $direction from pagination request")
|
||||||
}
|
}
|
||||||
TokenChunkEventPersistor.Result.REACHED_END -> {
|
TokenChunkEventPersistor.Result.REACHED_END -> {
|
||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE ->
|
TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE ->
|
||||||
|
|
|
@ -52,7 +52,6 @@ internal class DefaultTimelineService @AssistedInject constructor(
|
||||||
private val paginationTask: PaginationTask,
|
private val paginationTask: PaginationTask,
|
||||||
private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
|
private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
|
||||||
private val timelineEventMapper: TimelineEventMapper,
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
|
|
||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
private val readReceiptHandler: ReadReceiptHandler
|
private val readReceiptHandler: ReadReceiptHandler
|
||||||
) : TimelineService {
|
) : TimelineService {
|
||||||
|
@ -72,7 +71,6 @@ internal class DefaultTimelineService @AssistedInject constructor(
|
||||||
paginationTask = paginationTask,
|
paginationTask = paginationTask,
|
||||||
timelineEventMapper = timelineEventMapper,
|
timelineEventMapper = timelineEventMapper,
|
||||||
settings = settings,
|
settings = settings,
|
||||||
hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings),
|
|
||||||
timelineInput = timelineInput,
|
timelineInput = timelineInput,
|
||||||
eventDecryptor = eventDecryptor,
|
eventDecryptor = eventDecryptor,
|
||||||
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* 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
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.timeline
|
|
||||||
|
|
||||||
import io.realm.RealmQuery
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
|
||||||
import org.matrix.android.sdk.internal.database.query.filterEvents
|
|
||||||
|
|
||||||
internal fun RealmQuery<TimelineEventEntity>.filterEventsWithSettings(settings: TimelineSettings): RealmQuery<TimelineEventEntity> {
|
|
||||||
return filterEvents(settings.filters)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun List<TimelineEvent>.filterEventsWithSettings(settings: TimelineSettings): List<TimelineEvent> {
|
|
||||||
return filter { event ->
|
|
||||||
val filterType = !settings.filters.filterTypes
|
|
||||||
|| settings.filters.allowedTypes.any { it.eventType == event.root.type && (it.stateKey == null || it.stateKey == event.root.senderId) }
|
|
||||||
if (!filterType) return@filter false
|
|
||||||
|
|
||||||
val filterEdits = if (settings.filters.filterEdits && event.root.getClearType() == EventType.MESSAGE) {
|
|
||||||
val messageContent = event.root.getClearContent().toModel<MessageContent>()
|
|
||||||
messageContent?.relatesTo?.type != RelationType.REPLACE && messageContent?.relatesTo?.type != RelationType.RESPONSE
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
if (!filterEdits) return@filter false
|
|
||||||
|
|
||||||
val filterRedacted = settings.filters.filterRedacted && event.root.isRedacted()
|
|
||||||
!filterRedacted
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,195 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* 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
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.timeline
|
|
||||||
|
|
||||||
import android.util.SparseArray
|
|
||||||
import io.realm.OrderedRealmCollectionChangeListener
|
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmQuery
|
|
||||||
import io.realm.RealmResults
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
|
||||||
import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper
|
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity
|
|
||||||
import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
|
||||||
import org.matrix.android.sdk.internal.database.query.TimelineEventFilter
|
|
||||||
import org.matrix.android.sdk.internal.database.query.whereInRoom
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is responsible for handling the read receipts for hidden events (check [TimelineSettings] to see filtering).
|
|
||||||
* When an hidden event has read receipts, we want to transfer these read receipts on the first older displayed event.
|
|
||||||
* It has to be used in [DefaultTimeline] and we should call the [start] and [dispose] methods to properly handle realm subscription.
|
|
||||||
*/
|
|
||||||
internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
|
|
||||||
private val roomId: String,
|
|
||||||
private val settings: TimelineSettings) {
|
|
||||||
|
|
||||||
interface Delegate {
|
|
||||||
fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean
|
|
||||||
fun onReadReceiptsUpdated()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val correctedReadReceiptsEventByIndex = SparseArray<String>()
|
|
||||||
private val correctedReadReceiptsByEvent = HashMap<String, MutableList<ReadReceipt>>()
|
|
||||||
|
|
||||||
private lateinit var hiddenReadReceipts: RealmResults<ReadReceiptsSummaryEntity>
|
|
||||||
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 ->
|
|
||||||
if (!collection.isLoaded || !collection.isValid) {
|
|
||||||
return@OrderedRealmCollectionChangeListener
|
|
||||||
}
|
|
||||||
var hasChange = false
|
|
||||||
// Deletion here means we don't have any readReceipts for the given hidden events
|
|
||||||
changeSet.deletions.forEach {
|
|
||||||
val eventId = correctedReadReceiptsEventByIndex.get(it, "")
|
|
||||||
val timelineEvent = filteredEvents.where()
|
|
||||||
.equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
|
|
||||||
.findFirst()
|
|
||||||
|
|
||||||
// We are rebuilding the corresponding event with only his own RR
|
|
||||||
val readReceipts = readReceiptsSummaryMapper.map(timelineEvent?.readReceipts)
|
|
||||||
hasChange = delegate.rebuildEvent(eventId, readReceipts) || hasChange
|
|
||||||
}
|
|
||||||
correctedReadReceiptsEventByIndex.clear()
|
|
||||||
correctedReadReceiptsByEvent.clear()
|
|
||||||
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.displayIndex
|
|
||||||
|
|
||||||
if (isLoaded) {
|
|
||||||
// Then we are looking for the first displayable event after the hidden one
|
|
||||||
val firstDisplayedEvent = filteredEvents.where()
|
|
||||||
.lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, displayIndex)
|
|
||||||
.findFirst()
|
|
||||||
|
|
||||||
// If we find one, we should
|
|
||||||
if (firstDisplayedEvent != null) {
|
|
||||||
correctedReadReceiptsEventByIndex.put(index, firstDisplayedEvent.eventId)
|
|
||||||
correctedReadReceiptsByEvent
|
|
||||||
.getOrPut(firstDisplayedEvent.eventId, {
|
|
||||||
ArrayList(readReceiptsSummaryMapper.map(firstDisplayedEvent.readReceipts))
|
|
||||||
})
|
|
||||||
.addAll(readReceiptsSummaryMapper.map(summary))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (correctedReadReceiptsByEvent.isNotEmpty()) {
|
|
||||||
correctedReadReceiptsByEvent.forEach { (eventId, correctedReadReceipts) ->
|
|
||||||
val sortedReadReceipts = correctedReadReceipts.sortedByDescending {
|
|
||||||
it.originServerTs
|
|
||||||
}
|
|
||||||
hasChange = delegate.rebuildEvent(eventId, sortedReadReceipts) || hasChange
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasChange) {
|
|
||||||
delegate.onReadReceiptsUpdated()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the realm query subscription. Has to be called on an HandlerThread
|
|
||||||
*/
|
|
||||||
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).
|
|
||||||
this.hiddenReadReceipts = ReadReceiptsSummaryEntity.whereInRoom(realm, roomId)
|
|
||||||
.isNotEmpty(ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.`$`)
|
|
||||||
.isNotEmpty(ReadReceiptsSummaryEntityFields.READ_RECEIPTS.`$`)
|
|
||||||
.filterReceiptsWithSettings()
|
|
||||||
.findAllAsync()
|
|
||||||
.also { it.addChangeListener(hiddenReadReceiptsListener) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispose the realm query subscription. Has to be called on an HandlerThread
|
|
||||||
*/
|
|
||||||
fun dispose() {
|
|
||||||
if (this::hiddenReadReceipts.isInitialized) {
|
|
||||||
this.hiddenReadReceipts.removeAllChangeListeners()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the current corrected [ReadReceipt] list for an event, or null
|
|
||||||
*/
|
|
||||||
fun correctedReadReceipts(eventId: String?): List<ReadReceipt>? {
|
|
||||||
return correctedReadReceiptsByEvent[eventId]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We are looking for receipts related to filtered events. So, it's the opposite of [DefaultTimeline.filterEventsWithSettings] method.
|
|
||||||
*/
|
|
||||||
private fun RealmQuery<ReadReceiptsSummaryEntity>.filterReceiptsWithSettings(): RealmQuery<ReadReceiptsSummaryEntity> {
|
|
||||||
beginGroup()
|
|
||||||
var needOr = false
|
|
||||||
if (settings.filters.filterTypes) {
|
|
||||||
beginGroup()
|
|
||||||
// Events: A, B, C, D, (E and S1), F, G, (H and S1), I
|
|
||||||
// Allowed: A, B, C, (E and S1), G, (H and S2)
|
|
||||||
// Result: D, F, H, I
|
|
||||||
settings.filters.allowedTypes.forEachIndexed { index, filter ->
|
|
||||||
if (filter.stateKey == null) {
|
|
||||||
notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.TYPE}", filter.eventType)
|
|
||||||
} else {
|
|
||||||
beginGroup()
|
|
||||||
notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.TYPE}", filter.eventType)
|
|
||||||
or()
|
|
||||||
notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.STATE_KEY}", filter.stateKey)
|
|
||||||
endGroup()
|
|
||||||
}
|
|
||||||
if (index != settings.filters.allowedTypes.size - 1) {
|
|
||||||
and()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
endGroup()
|
|
||||||
needOr = true
|
|
||||||
}
|
|
||||||
if (settings.filters.filterUseless) {
|
|
||||||
if (needOr) or()
|
|
||||||
equalTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.IS_USELESS}", true)
|
|
||||||
needOr = true
|
|
||||||
}
|
|
||||||
if (settings.filters.filterEdits) {
|
|
||||||
if (needOr) or()
|
|
||||||
like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.CONTENT}", TimelineEventFilter.Content.EDIT)
|
|
||||||
or()
|
|
||||||
like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.CONTENT}", TimelineEventFilter.Content.RESPONSE)
|
|
||||||
needOr = true
|
|
||||||
}
|
|
||||||
if (settings.filters.filterRedacted) {
|
|
||||||
if (needOr) or()
|
|
||||||
like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.UNSIGNED_DATA}", TimelineEventFilter.Unsigned.REDACTED)
|
|
||||||
}
|
|
||||||
endGroup()
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -70,15 +70,13 @@ internal class UIEchoManager(
|
||||||
return existingState != sendState
|
return existingState != sendState
|
||||||
}
|
}
|
||||||
|
|
||||||
// return true if should update
|
fun onLocalEchoCreated(timelineEvent: TimelineEvent) {
|
||||||
fun onLocalEchoCreated(timelineEvent: TimelineEvent): Boolean {
|
|
||||||
var postSnapshot = false
|
|
||||||
|
|
||||||
// Manage some ui echos (do it before filter because actual event could be filtered out)
|
// Manage some ui echos (do it before filter because actual event could be filtered out)
|
||||||
when (timelineEvent.root.getClearType()) {
|
when (timelineEvent.root.getClearType()) {
|
||||||
EventType.REDACTION -> {
|
EventType.REDACTION -> {
|
||||||
}
|
}
|
||||||
EventType.REACTION -> {
|
EventType.REACTION -> {
|
||||||
val content = timelineEvent.root.content?.toModel<ReactionContent>()
|
val content = timelineEvent.root.content?.toModel<ReactionContent>()
|
||||||
if (RelationType.ANNOTATION == content?.relatesTo?.type) {
|
if (RelationType.ANNOTATION == content?.relatesTo?.type) {
|
||||||
val reaction = content.relatesTo.key
|
val reaction = content.relatesTo.key
|
||||||
|
@ -91,21 +89,14 @@ internal class UIEchoManager(
|
||||||
reaction = reaction
|
reaction = reaction
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
postSnapshot = listener.rebuildEvent(relatedEventID) {
|
listener.rebuildEvent(relatedEventID) {
|
||||||
decorateEventWithReactionUiEcho(it)
|
decorateEventWithReactionUiEcho(it)
|
||||||
} || postSnapshot
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Timber.v("On local echo created: ${timelineEvent.eventId}")
|
||||||
// do not add events that would have been filtered
|
inMemorySendingEvents.add(0, timelineEvent)
|
||||||
if (listOf(timelineEvent).filterEventsWithSettings(settings).isNotEmpty()) {
|
|
||||||
Timber.v("On local echo created: ${timelineEvent.eventId}")
|
|
||||||
inMemorySendingEvents.add(0, timelineEvent)
|
|
||||||
postSnapshot = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return postSnapshot
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decorateEventWithReactionUiEcho(timelineEvent: TimelineEvent): TimelineEvent? {
|
fun decorateEventWithReactionUiEcho(timelineEvent: TimelineEvent): TimelineEvent? {
|
||||||
|
|
|
@ -1205,7 +1205,6 @@ class RoomDetailFragment @Inject constructor(
|
||||||
if (summary?.membership == Membership.JOIN) {
|
if (summary?.membership == Membership.JOIN) {
|
||||||
views.jumpToBottomView.count = summary.notificationCount
|
views.jumpToBottomView.count = summary.notificationCount
|
||||||
views.jumpToBottomView.drawBadge = summary.hasUnreadMessages
|
views.jumpToBottomView.drawBadge = summary.hasUnreadMessages
|
||||||
scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline
|
|
||||||
timelineEventController.update(state)
|
timelineEventController.update(state)
|
||||||
views.inviteView.visibility = View.GONE
|
views.inviteView.visibility = View.GONE
|
||||||
if (state.tombstoneEvent == null) {
|
if (state.tombstoneEvent == null) {
|
||||||
|
|
|
@ -1156,16 +1156,15 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
private fun handleNavigateToEvent(action: RoomDetailAction.NavigateToEvent) {
|
private fun handleNavigateToEvent(action: RoomDetailAction.NavigateToEvent) {
|
||||||
stopTrackingUnreadMessages()
|
stopTrackingUnreadMessages()
|
||||||
val targetEventId: String = action.eventId
|
val targetEventId: String = action.eventId
|
||||||
val correctedEventId = timeline.getFirstDisplayableEventId(targetEventId) ?: targetEventId
|
val indexOfEvent = timeline.getIndexOfEvent(targetEventId)
|
||||||
val indexOfEvent = timeline.getIndexOfEvent(correctedEventId)
|
|
||||||
if (indexOfEvent == null) {
|
if (indexOfEvent == null) {
|
||||||
// Event is not already in RAM
|
// Event is not already in RAM
|
||||||
timeline.restartWithEventId(targetEventId)
|
timeline.restartWithEventId(targetEventId)
|
||||||
}
|
}
|
||||||
if (action.highlight) {
|
if (action.highlight) {
|
||||||
setState { copy(highlightedEventId = correctedEventId) }
|
setState { copy(highlightedEventId = targetEventId) }
|
||||||
}
|
}
|
||||||
_viewEvents.post(RoomDetailViewEvents.NavigateToEvent(correctedEventId))
|
_viewEvents.post(RoomDetailViewEvents.NavigateToEvent(targetEventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleResendEvent(action: RoomDetailAction.ResendMessage) {
|
private fun handleResendEvent(action: RoomDetailAction.ResendMessage) {
|
||||||
|
@ -1389,15 +1388,12 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
private fun computeUnreadState(events: List<TimelineEvent>, roomSummary: RoomSummary): UnreadState {
|
private fun computeUnreadState(events: List<TimelineEvent>, roomSummary: RoomSummary): UnreadState {
|
||||||
if (events.isEmpty()) return UnreadState.Unknown
|
if (events.isEmpty()) return UnreadState.Unknown
|
||||||
val readMarkerIdSnapshot = roomSummary.readMarkerId ?: return UnreadState.Unknown
|
val readMarkerIdSnapshot = roomSummary.readMarkerId ?: return UnreadState.Unknown
|
||||||
val firstDisplayableEventId = timeline.getFirstDisplayableEventId(readMarkerIdSnapshot)
|
val firstDisplayableEventIndex = timeline.getIndexOfEvent(readMarkerIdSnapshot)
|
||||||
val firstDisplayableEventIndex = timeline.getIndexOfEvent(firstDisplayableEventId)
|
?: return if (timeline.isLive) {
|
||||||
if (firstDisplayableEventId == null || firstDisplayableEventIndex == null) {
|
UnreadState.ReadMarkerNotLoaded(readMarkerIdSnapshot)
|
||||||
return if (timeline.isLive) {
|
} else {
|
||||||
UnreadState.ReadMarkerNotLoaded(readMarkerIdSnapshot)
|
UnreadState.Unknown
|
||||||
} else {
|
}
|
||||||
UnreadState.Unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (i in (firstDisplayableEventIndex - 1) downTo 0) {
|
for (i in (firstDisplayableEventIndex - 1) downTo 0) {
|
||||||
val timelineEvent = events.getOrNull(i) ?: return UnreadState.Unknown
|
val timelineEvent = events.getOrNull(i) ?: return UnreadState.Unknown
|
||||||
val eventId = timelineEvent.root.eventId ?: return UnreadState.Unknown
|
val eventId = timelineEvent.root.eventId ?: return UnreadState.Unknown
|
||||||
|
|
|
@ -33,8 +33,6 @@ class ScrollOnHighlightedEventCallback(private val recyclerView: RecyclerView,
|
||||||
|
|
||||||
private val scheduledEventId = AtomicReference<String?>()
|
private val scheduledEventId = AtomicReference<String?>()
|
||||||
|
|
||||||
var timeline: Timeline? = null
|
|
||||||
|
|
||||||
override fun onInserted(position: Int, count: Int) {
|
override fun onInserted(position: Int, count: Int) {
|
||||||
scrollIfNeeded()
|
scrollIfNeeded()
|
||||||
}
|
}
|
||||||
|
@ -45,9 +43,7 @@ class ScrollOnHighlightedEventCallback(private val recyclerView: RecyclerView,
|
||||||
|
|
||||||
private fun scrollIfNeeded() {
|
private fun scrollIfNeeded() {
|
||||||
val eventId = scheduledEventId.get() ?: return
|
val eventId = scheduledEventId.get() ?: return
|
||||||
val nonNullTimeline = timeline ?: return
|
val positionToScroll = timelineEventController.searchPositionOfEvent(eventId)
|
||||||
val correctedEventId = nonNullTimeline.getFirstDisplayableEventId(eventId)
|
|
||||||
val positionToScroll = timelineEventController.searchPositionOfEvent(correctedEventId)
|
|
||||||
if (positionToScroll != null) {
|
if (positionToScroll != null) {
|
||||||
val firstVisibleItem = layoutManager.findFirstCompletelyVisibleItemPosition()
|
val firstVisibleItem = layoutManager.findFirstCompletelyVisibleItemPosition()
|
||||||
val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition()
|
val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition()
|
||||||
|
|
|
@ -17,48 +17,14 @@
|
||||||
package im.vector.app.features.home.room.detail.timeline.helper
|
package im.vector.app.features.home.room.detail.timeline.helper
|
||||||
|
|
||||||
import im.vector.app.core.resources.UserPreferencesProvider
|
import im.vector.app.core.resources.UserPreferencesProvider
|
||||||
import org.matrix.android.sdk.api.session.Session
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.EventTypeFilter
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimelineSettingsFactory @Inject constructor(
|
class TimelineSettingsFactory @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) {
|
||||||
private val userPreferencesProvider: UserPreferencesProvider,
|
|
||||||
private val session: Session
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun create(): TimelineSettings {
|
fun create(): TimelineSettings {
|
||||||
return if (userPreferencesProvider.shouldShowHiddenEvents()) {
|
return TimelineSettings(
|
||||||
TimelineSettings(
|
initialSize = 30,
|
||||||
initialSize = 30,
|
buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
|
||||||
filters = TimelineEventFilters(
|
|
||||||
filterEdits = false,
|
|
||||||
filterRedacted = userPreferencesProvider.shouldShowRedactedMessages().not(),
|
|
||||||
filterUseless = false,
|
|
||||||
filterTypes = false),
|
|
||||||
buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
|
|
||||||
} else {
|
|
||||||
val allowedTypes = TimelineDisplayableEvents.DISPLAYABLE_TYPES.createAllowedEventTypeFilters()
|
|
||||||
TimelineSettings(
|
|
||||||
initialSize = 30,
|
|
||||||
filters = TimelineEventFilters(
|
|
||||||
filterEdits = true,
|
|
||||||
filterRedacted = userPreferencesProvider.shouldShowRedactedMessages().not(),
|
|
||||||
filterUseless = true,
|
|
||||||
filterTypes = true,
|
|
||||||
allowedTypes = allowedTypes),
|
|
||||||
buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun List<String>.createAllowedEventTypeFilters(): List<EventTypeFilter> {
|
|
||||||
return map {
|
|
||||||
EventTypeFilter(
|
|
||||||
eventType = it,
|
|
||||||
stateKey = if (it == EventType.STATE_ROOM_MEMBER && !userPreferencesProvider.shouldShowRoomMemberStateEvents()) session.myUserId else null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue