mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-23 09:56:00 +03:00
Merging redacted events by reusing existing mechanism for same type events
This commit is contained in:
parent
8d8ee051eb
commit
b412b9f4e9
3 changed files with 127 additions and 24 deletions
|
@ -46,6 +46,8 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val MIN_NUMBER_OF_MERGED_EVENTS = 2
|
||||||
|
|
||||||
class MergedHeaderItemFactory @Inject constructor(
|
class MergedHeaderItemFactory @Inject constructor(
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
@ -80,10 +82,12 @@ class MergedHeaderItemFactory @Inject constructor(
|
||||||
requestModelBuild: () -> Unit
|
requestModelBuild: () -> Unit
|
||||||
): BasedMergedItem<*>? {
|
): BasedMergedItem<*>? {
|
||||||
return when {
|
return when {
|
||||||
isRoomCreationSummary(event, nextEvent) ->
|
isStartOfRoomCreationSummary(event, nextEvent) ->
|
||||||
buildRoomCreationMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
buildRoomCreationMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
||||||
isSimilarEventSummary(event, nextEvent, addDaySeparator) ->
|
isStartOfSameTypeEventsSummary(event, nextEvent, addDaySeparator) ->
|
||||||
buildSimilarEventsMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
buildSameTypeEventsMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
||||||
|
isStartOfRedactedEventsSummary(event, nextEvent, addDaySeparator) ->
|
||||||
|
buildRedactedEventsMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +96,7 @@ class MergedHeaderItemFactory @Inject constructor(
|
||||||
* @param event the main timeline event
|
* @param event the main timeline event
|
||||||
* @param nextEvent is an older event than event
|
* @param nextEvent is an older event than event
|
||||||
*/
|
*/
|
||||||
private fun isRoomCreationSummary(
|
private fun isStartOfRoomCreationSummary(
|
||||||
event: TimelineEvent,
|
event: TimelineEvent,
|
||||||
nextEvent: TimelineEvent?,
|
nextEvent: TimelineEvent?,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
|
@ -107,7 +111,7 @@ class MergedHeaderItemFactory @Inject constructor(
|
||||||
* @param nextEvent is an older event than event
|
* @param nextEvent is an older event than event
|
||||||
* @param addDaySeparator true to add a day separator
|
* @param addDaySeparator true to add a day separator
|
||||||
*/
|
*/
|
||||||
private fun isSimilarEventSummary(
|
private fun isStartOfSameTypeEventsSummary(
|
||||||
event: TimelineEvent,
|
event: TimelineEvent,
|
||||||
nextEvent: TimelineEvent?,
|
nextEvent: TimelineEvent?,
|
||||||
addDaySeparator: Boolean,
|
addDaySeparator: Boolean,
|
||||||
|
@ -116,7 +120,21 @@ class MergedHeaderItemFactory @Inject constructor(
|
||||||
(nextEvent?.root?.getClearType() != event.root.getClearType() || addDaySeparator)
|
(nextEvent?.root?.getClearType() != event.root.getClearType() || addDaySeparator)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildSimilarEventsMergedSummary(
|
/**
|
||||||
|
* @param event the main timeline event
|
||||||
|
* @param nextEvent is an older event than event
|
||||||
|
* @param addDaySeparator true to add a day separator
|
||||||
|
*/
|
||||||
|
private fun isStartOfRedactedEventsSummary(
|
||||||
|
event: TimelineEvent,
|
||||||
|
nextEvent: TimelineEvent?,
|
||||||
|
addDaySeparator: Boolean,
|
||||||
|
): Boolean {
|
||||||
|
return event.root.isRedacted() &&
|
||||||
|
((nextEvent?.root?.getClearType() != EventType.REDACTION && !nextEvent?.root?.isRedacted().orFalse()) || addDaySeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildSameTypeEventsMergedSummary(
|
||||||
currentPosition: Int,
|
currentPosition: Int,
|
||||||
items: List<TimelineEvent>,
|
items: List<TimelineEvent>,
|
||||||
partialState: TimelineEventController.PartialState,
|
partialState: TimelineEventController.PartialState,
|
||||||
|
@ -128,11 +146,42 @@ class MergedHeaderItemFactory @Inject constructor(
|
||||||
val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(
|
val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(
|
||||||
items,
|
items,
|
||||||
currentPosition,
|
currentPosition,
|
||||||
2,
|
MIN_NUMBER_OF_MERGED_EVENTS,
|
||||||
eventIdToHighlight,
|
eventIdToHighlight,
|
||||||
partialState.rootThreadEventId,
|
partialState.rootThreadEventId,
|
||||||
partialState.isFromThreadTimeline()
|
partialState.isFromThreadTimeline()
|
||||||
)
|
)
|
||||||
|
return buildSimilarEventsMergedSummary(mergedEvents, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildRedactedEventsMergedSummary(
|
||||||
|
currentPosition: Int,
|
||||||
|
items: List<TimelineEvent>,
|
||||||
|
partialState: TimelineEventController.PartialState,
|
||||||
|
event: TimelineEvent,
|
||||||
|
eventIdToHighlight: String?,
|
||||||
|
requestModelBuild: () -> Unit,
|
||||||
|
callback: TimelineEventController.Callback?
|
||||||
|
): MergedSimilarEventsItem_? {
|
||||||
|
val mergedEvents = timelineEventVisibilityHelper.prevRedactedEvents(
|
||||||
|
items,
|
||||||
|
currentPosition,
|
||||||
|
MIN_NUMBER_OF_MERGED_EVENTS,
|
||||||
|
eventIdToHighlight,
|
||||||
|
partialState.rootThreadEventId,
|
||||||
|
partialState.isFromThreadTimeline()
|
||||||
|
)
|
||||||
|
return buildSimilarEventsMergedSummary(mergedEvents, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildSimilarEventsMergedSummary(
|
||||||
|
mergedEvents: List<TimelineEvent>,
|
||||||
|
partialState: TimelineEventController.PartialState,
|
||||||
|
event: TimelineEvent,
|
||||||
|
eventIdToHighlight: String?,
|
||||||
|
requestModelBuild: () -> Unit,
|
||||||
|
callback: TimelineEventController.Callback?
|
||||||
|
): MergedSimilarEventsItem_? {
|
||||||
return if (mergedEvents.isEmpty()) {
|
return if (mergedEvents.isEmpty()) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
|
@ -153,7 +202,7 @@ class MergedHeaderItemFactory @Inject constructor(
|
||||||
)
|
)
|
||||||
mergedData.add(data)
|
mergedData.add(data)
|
||||||
}
|
}
|
||||||
val mergedEventIds = mergedEvents.map { it.localId }
|
val mergedEventIds = mergedEvents.map { it.localId }.toSet()
|
||||||
// We try to find if one of the item id were used as mergeItemCollapseStates key
|
// 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
|
// => handle case where paginating from mergeable events and we get more
|
||||||
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
|
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
|
||||||
|
@ -223,7 +272,7 @@ class MergedHeaderItemFactory @Inject constructor(
|
||||||
tmpPos--
|
tmpPos--
|
||||||
prevEvent = items.getOrNull(tmpPos)
|
prevEvent = items.getOrNull(tmpPos)
|
||||||
}
|
}
|
||||||
return if (mergedEvents.size > 2) {
|
return if (mergedEvents.size > MIN_NUMBER_OF_MERGED_EVENTS) {
|
||||||
var highlighted = false
|
var highlighted = false
|
||||||
val mergedData = ArrayList<BasedMergedItem.Data>(mergedEvents.size)
|
val mergedData = ArrayList<BasedMergedItem.Data>(mergedEvents.size)
|
||||||
mergedEvents.reversed()
|
mergedEvents.reversed()
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.helper
|
||||||
|
|
||||||
import im.vector.app.core.extensions.localDateTime
|
import im.vector.app.core.extensions.localDateTime
|
||||||
import im.vector.app.core.resources.UserPreferencesProvider
|
import im.vector.app.core.resources.UserPreferencesProvider
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
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.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
import org.matrix.android.sdk.api.session.events.model.getRelationContent
|
import org.matrix.android.sdk.api.session.events.model.getRelationContent
|
||||||
|
@ -30,25 +31,38 @@ import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimelineEventVisibilityHelper @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) {
|
class TimelineEventVisibilityHelper @Inject constructor(
|
||||||
|
private val userPreferencesProvider: UserPreferencesProvider,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private interface PredicateToStopSearch {
|
||||||
|
/**
|
||||||
|
* Indicate whether a search on events should stop by comparing to given successive events.
|
||||||
|
* @param oldEvent the oldest event between the 2 events to compare
|
||||||
|
* @param newEvent the more recent event between the 2 events to compare
|
||||||
|
*/
|
||||||
|
fun shouldStopSearch(oldEvent: Event, newEvent: Event): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param timelineEvents the events to search in
|
* @param timelineEvents the events to search in, sorted from oldest event to newer event
|
||||||
* @param index the index to start computing (inclusive)
|
* @param index the index to start computing (inclusive)
|
||||||
* @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list
|
* @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list
|
||||||
* @param eventIdToHighlight used to compute visibility
|
* @param eventIdToHighlight used to compute visibility
|
||||||
* @param rootThreadEventId the root thread event id if in a thread timeline
|
* @param rootThreadEventId the root thread event id if in a thread timeline
|
||||||
* @param isFromThreadTimeline true if the timeline is a thread
|
* @param isFromThreadTimeline true if the timeline is a thread
|
||||||
|
* @param predicateToStop events are taken until this condition is met
|
||||||
*
|
*
|
||||||
* @return a list of timeline events which have sequentially the same type following the next direction.
|
* @return a list of timeline events which meet sequentially the same criteria following the next direction.
|
||||||
*/
|
*/
|
||||||
private fun nextSameTypeEvents(
|
private fun nextEventsUntil(
|
||||||
timelineEvents: List<TimelineEvent>,
|
timelineEvents: List<TimelineEvent>,
|
||||||
index: Int,
|
index: Int,
|
||||||
minSize: Int,
|
minSize: Int,
|
||||||
eventIdToHighlight: String?,
|
eventIdToHighlight: String?,
|
||||||
rootThreadEventId: String?,
|
rootThreadEventId: String?,
|
||||||
isFromThreadTimeline: Boolean
|
isFromThreadTimeline: Boolean,
|
||||||
|
predicateToStop: PredicateToStopSearch
|
||||||
): List<TimelineEvent> {
|
): List<TimelineEvent> {
|
||||||
if (index >= timelineEvents.size - 1) {
|
if (index >= timelineEvents.size - 1) {
|
||||||
return emptyList()
|
return emptyList()
|
||||||
|
@ -65,13 +79,15 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
|
||||||
} else {
|
} else {
|
||||||
nextSubList.subList(0, indexOfNextDay)
|
nextSubList.subList(0, indexOfNextDay)
|
||||||
}
|
}
|
||||||
val indexOfFirstDifferentEventType = nextSameDayEvents.indexOfFirst { it.root.getClearType() != timelineEvent.root.getClearType() }
|
val indexOfFirstDifferentEvent = nextSameDayEvents.indexOfFirst {
|
||||||
val sameTypeEvents = if (indexOfFirstDifferentEventType == -1) {
|
predicateToStop.shouldStopSearch(oldEvent = timelineEvent.root, newEvent = it.root)
|
||||||
|
}
|
||||||
|
val similarEvents = if (indexOfFirstDifferentEvent == -1) {
|
||||||
nextSameDayEvents
|
nextSameDayEvents
|
||||||
} else {
|
} else {
|
||||||
nextSameDayEvents.subList(0, indexOfFirstDifferentEventType)
|
nextSameDayEvents.subList(0, indexOfFirstDifferentEvent)
|
||||||
}
|
}
|
||||||
val filteredSameTypeEvents = sameTypeEvents.filter {
|
val filteredSimilarEvents = similarEvents.filter {
|
||||||
shouldShowEvent(
|
shouldShowEvent(
|
||||||
timelineEvent = it,
|
timelineEvent = it,
|
||||||
highlightedEventId = eventIdToHighlight,
|
highlightedEventId = eventIdToHighlight,
|
||||||
|
@ -79,14 +95,11 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
|
||||||
rootThreadEventId = rootThreadEventId
|
rootThreadEventId = rootThreadEventId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (filteredSameTypeEvents.size < minSize) {
|
return if (filteredSimilarEvents.size < minSize) emptyList() else filteredSimilarEvents
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
return filteredSameTypeEvents
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param timelineEvents the events to search in
|
* @param timelineEvents the events to search in, sorted from newer event to oldest event
|
||||||
* @param index the index to start computing (inclusive)
|
* @param index the index to start computing (inclusive)
|
||||||
* @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list
|
* @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list
|
||||||
* @param eventIdToHighlight used to compute visibility
|
* @param eventIdToHighlight used to compute visibility
|
||||||
|
@ -107,7 +120,44 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
|
||||||
return prevSub
|
return prevSub
|
||||||
.reversed()
|
.reversed()
|
||||||
.let {
|
.let {
|
||||||
nextSameTypeEvents(it, 0, minSize, eventIdToHighlight, rootThreadEventId, isFromThreadTimeline)
|
nextEventsUntil(it, 0, minSize, eventIdToHighlight, rootThreadEventId, isFromThreadTimeline, object : PredicateToStopSearch {
|
||||||
|
override fun shouldStopSearch(oldEvent: Event, newEvent: Event): Boolean {
|
||||||
|
return oldEvent.getClearType() != newEvent.getClearType()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param timelineEvents the events to search in, sorted from newer event to oldest event
|
||||||
|
* @param index the index to start computing (inclusive)
|
||||||
|
* @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list
|
||||||
|
* @param eventIdToHighlight used to compute visibility
|
||||||
|
* @param rootThreadEventId the root thread eventId
|
||||||
|
* @param isFromThreadTimeline true if the timeline is a thread
|
||||||
|
*
|
||||||
|
* @return a list of timeline events which are all redacted following the prev direction.
|
||||||
|
*/
|
||||||
|
fun prevRedactedEvents(
|
||||||
|
timelineEvents: List<TimelineEvent>,
|
||||||
|
index: Int,
|
||||||
|
minSize: Int,
|
||||||
|
eventIdToHighlight: String?,
|
||||||
|
rootThreadEventId: String?,
|
||||||
|
isFromThreadTimeline: Boolean
|
||||||
|
): List<TimelineEvent> {
|
||||||
|
val prevSub = timelineEvents
|
||||||
|
.subList(0, index + 1)
|
||||||
|
// Ensure to not take the REDACTION event into account
|
||||||
|
.filter { it.root.getClearType() != EventType.REDACTION }
|
||||||
|
return prevSub
|
||||||
|
.reversed()
|
||||||
|
.let {
|
||||||
|
nextEventsUntil(it, 0, minSize, eventIdToHighlight, rootThreadEventId, isFromThreadTimeline, object : PredicateToStopSearch {
|
||||||
|
override fun shouldStopSearch(oldEvent: Event, newEvent: Event): Boolean {
|
||||||
|
return oldEvent.isRedacted() && !newEvent.isRedacted()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3103,4 +3103,8 @@
|
||||||
<string name="live_location_labs_promotion_description">Please note: this is a labs feature using a temporary implementation. This means you will not be able to delete your location history, and advanced users will be able to see your location history even after you stop sharing your live location with this room.</string>
|
<string name="live_location_labs_promotion_description">Please note: this is a labs feature using a temporary implementation. This means you will not be able to delete your location history, and advanced users will be able to see your location history even after you stop sharing your live location with this room.</string>
|
||||||
<string name="live_location_labs_promotion_switch_title">Enable location sharing</string>
|
<string name="live_location_labs_promotion_switch_title">Enable location sharing</string>
|
||||||
|
|
||||||
|
<plurals name="room_removed_messages">
|
||||||
|
<item quantity="one">%d message removed</item>
|
||||||
|
<item quantity="other">%d messages removed</item>
|
||||||
|
</plurals>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue