Make TimelineSettings aware of rootThreadEventId and welcome a new Thread mode for the timeline creation

This commit is contained in:
ariskotsomitopoulos 2022-01-10 11:20:31 +02:00
parent 50e51cbe29
commit e541636802
8 changed files with 79 additions and 21 deletions

View file

@ -27,5 +27,14 @@ data class TimelineSettings(
/** /**
* If true, will build read receipts for each event. * If true, will build read receipts for each event.
*/ */
val buildReadReceipts: Boolean = true val buildReadReceipts: Boolean = true,
) /**
* The root thread eventId if this is a thread timeline, or null if this is NOT a thread timeline
*/
val rootThreadEventId: String? = null) {
/**
* Returns true if this is a thread timeline or false otherwise
*/
fun isThreadTimeline() = rootThreadEventId != null
}

View file

@ -136,7 +136,7 @@ internal class DefaultTimeline(private val roomId: String,
ensureReadReceiptAreLoaded(realm) ensureReadReceiptAreLoaded(realm)
backgroundRealm.set(realm) backgroundRealm.set(realm)
listenToPostSnapshotSignals() listenToPostSnapshotSignals()
openAround(initialEventId) openAround(initialEventId, rootThreadEventId)
postSnapshot() postSnapshot()
} }
} }
@ -157,7 +157,7 @@ internal class DefaultTimeline(private val roomId: String,
override fun restartWithEventId(eventId: String?) { override fun restartWithEventId(eventId: String?) {
timelineScope.launch { timelineScope.launch {
openAround(eventId) openAround(eventId,rootThreadEventId)
postSnapshot() postSnapshot()
} }
} }
@ -226,18 +226,20 @@ internal class DefaultTimeline(private val roomId: String,
return true return true
} }
private suspend fun openAround(eventId: String?) = withContext(timelineDispatcher) { private suspend fun openAround(eventId: String?, rootThreadEventId: String?) = withContext(timelineDispatcher) {
val baseLogMessage = "openAround(eventId: $eventId)" val baseLogMessage = "openAround(eventId: $eventId)"
Timber.v("$baseLogMessage started") Timber.v("$baseLogMessage started")
if (!isStarted.get()) { if (!isStarted.get()) {
throw IllegalStateException("You should call start before using timeline") throw IllegalStateException("You should call start before using timeline")
} }
strategy.onStop() strategy.onStop()
strategy = if (eventId == null) {
buildStrategy(LoadTimelineStrategy.Mode.Live) strategy = when {
} else { rootThreadEventId != null -> buildStrategy(LoadTimelineStrategy.Mode.Thread(rootThreadEventId))
buildStrategy(LoadTimelineStrategy.Mode.Permalink(eventId)) eventId == null -> buildStrategy(LoadTimelineStrategy.Mode.Live)
else -> buildStrategy(LoadTimelineStrategy.Mode.Permalink(eventId))
} }
initPaginationStates(eventId) initPaginationStates(eventId)
strategy.onStart() strategy.onStart()
loadMore( loadMore(

View file

@ -20,4 +20,5 @@ internal enum class LoadMoreResult {
REACHED_END, REACHED_END,
SUCCESS, SUCCESS,
FAILURE FAILURE
// evenIDS
} }

View file

@ -51,6 +51,7 @@ internal class LoadTimelineStrategy(
sealed interface Mode { sealed interface Mode {
object Live : Mode object Live : Mode
data class Permalink(val originEventId: String) : Mode data class Permalink(val originEventId: String) : Mode
data class Thread(val rootThreadEventId: String) : Mode
fun originEventId(): String? { fun originEventId(): String? {
return if (this is Permalink) { return if (this is Permalink) {
@ -59,6 +60,14 @@ internal class LoadTimelineStrategy(
null null
} }
} }
// fun getRootThreadEventId(): String? {
// return if (this is Thread) {
// rootThreadEventId
// } else {
// null
// }
// }
} }
data class Dependencies( data class Dependencies(
@ -162,6 +171,7 @@ internal class LoadTimelineStrategy(
} }
suspend fun loadMore(count: Int, direction: Timeline.Direction, fetchOnServerIfNeeded: Boolean = true): LoadMoreResult { suspend fun loadMore(count: Int, direction: Timeline.Direction, fetchOnServerIfNeeded: Boolean = true): LoadMoreResult {
///
if (mode is Mode.Permalink && timelineChunk == null) { if (mode is Mode.Permalink && timelineChunk == null) {
val params = GetContextOfEventTask.Params(roomId, mode.originEventId) val params = GetContextOfEventTask.Params(roomId, mode.originEventId)
try { try {
@ -198,13 +208,22 @@ internal class LoadTimelineStrategy(
} }
private fun getChunkEntity(realm: Realm): RealmResults<ChunkEntity> { private fun getChunkEntity(realm: Realm): RealmResults<ChunkEntity> {
return if (mode is Mode.Permalink) {
ChunkEntity.findAllIncludingEvents(realm, listOf(mode.originEventId)) return when (mode) {
} else { is Mode.Live -> {
ChunkEntity.where(realm, roomId) ChunkEntity.where(realm, roomId)
.equalTo(ChunkEntityFields.IS_LAST_FORWARD, true) .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true)
.findAll() .findAll()
} }
is Mode.Permalink -> {
ChunkEntity.findAllIncludingEvents(realm, listOf(mode.originEventId))
}
is Mode.Thread -> {
ChunkEntity.where(realm, roomId)
.equalTo(ChunkEntityFields.IS_LAST_FORWARD, true)
.findAll()
}
}
} }
private fun hasReachedLastForward(): Boolean { private fun hasReachedLastForward(): Boolean {

View file

@ -23,6 +23,7 @@ import io.realm.RealmQuery
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.Sort import io.realm.Sort
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import org.matrix.android.sdk.BuildConfig
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
@ -271,7 +272,24 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
private suspend fun loadFromStorage(count: Int, direction: Timeline.Direction): Int { private suspend fun loadFromStorage(count: Int, direction: Timeline.Direction): Int {
val displayIndex = getNextDisplayIndex(direction) ?: return 0 val displayIndex = getNextDisplayIndex(direction) ?: return 0
val baseQuery = timelineEventEntities.where() val baseQuery = timelineEventEntities.where()
val timelineEvents = baseQuery.offsets(direction, count, displayIndex).findAll().orEmpty()
val timelineEvents = if (timelineSettings.rootThreadEventId != null) {
baseQuery
.beginGroup()
.equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, timelineSettings.rootThreadEventId)
.or()
.equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, timelineSettings.rootThreadEventId)
.endGroup()
.offsets(direction, count, displayIndex)
.findAll()
.orEmpty()
} else {
baseQuery
.offsets(direction, count, displayIndex)
.findAll()
.orEmpty()
}
if (timelineEvents.isEmpty()) return 0 if (timelineEvents.isEmpty()) return 0
fetchRootThreadEventsIfNeeded(timelineEvents) fetchRootThreadEventsIfNeeded(timelineEvents)
if (direction == Timeline.Direction.FORWARDS) { if (direction == Timeline.Direction.FORWARDS) {
@ -299,6 +317,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
* in order to be able to display the event to the user appropriately * in order to be able to display the event to the user appropriately
*/ */
private suspend fun fetchRootThreadEventsIfNeeded(offsetResults: List<TimelineEventEntity>) { private suspend fun fetchRootThreadEventsIfNeeded(offsetResults: List<TimelineEventEntity>) {
if (BuildConfig.THREADING_ENABLED) return
val eventEntityList = offsetResults val eventEntityList = offsetResults
.mapNotNull { .mapNotNull {
it.root it.root

View file

@ -127,7 +127,7 @@ class RoomDetailViewModel @AssistedInject constructor(
private val invisibleEventsSource = BehaviorDataSource<RoomDetailAction.TimelineEventTurnsInvisible>() private val invisibleEventsSource = BehaviorDataSource<RoomDetailAction.TimelineEventTurnsInvisible>()
private val visibleEventsSource = BehaviorDataSource<RoomDetailAction.TimelineEventTurnsVisible>() private val visibleEventsSource = BehaviorDataSource<RoomDetailAction.TimelineEventTurnsVisible>()
private var timelineEvents = MutableSharedFlow<List<TimelineEvent>>(0) private var timelineEvents = MutableSharedFlow<List<TimelineEvent>>(0)
val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId) val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId, initialState.rootThreadEventId)
// Same lifecycle than the ViewModel (survive to screen rotation) // Same lifecycle than the ViewModel (survive to screen rotation)
val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope) val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope)

View file

@ -35,8 +35,14 @@ private val secondaryTimelineAllowedTypes = listOf(
class TimelineFactory @Inject constructor(private val session: Session, private val timelineSettingsFactory: TimelineSettingsFactory) { class TimelineFactory @Inject constructor(private val session: Session, private val timelineSettingsFactory: TimelineSettingsFactory) {
fun createTimeline(coroutineScope: CoroutineScope, mainRoom: Room, eventId: String?): Timeline { fun createTimeline(
val settings = timelineSettingsFactory.create() coroutineScope: CoroutineScope,
mainRoom: Room,
eventId: String?,
rootThreadEventId: String?
): Timeline {
val settings = timelineSettingsFactory.create(rootThreadEventId)
if (!session.vectorCallService.protocolChecker.supportVirtualRooms) { if (!session.vectorCallService.protocolChecker.supportVirtualRooms) {
return mainRoom.createTimeline(eventId, settings) return mainRoom.createTimeline(eventId, settings)
} }

View file

@ -22,9 +22,11 @@ import javax.inject.Inject
class TimelineSettingsFactory @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) { class TimelineSettingsFactory @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) {
fun create(): TimelineSettings { fun create(rootThreadEventId: String?): TimelineSettings {
return TimelineSettings( return TimelineSettings(
initialSize = 30, initialSize = 30,
buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts()) buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts(),
rootThreadEventId = rootThreadEventId
)
} }
} }