Timeline : makes sure all tests are passing
This commit is contained in:
parent
bffa2d717f
commit
97b9d75a0d
12 changed files with 108 additions and 49 deletions
|
|
@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.api.core.EventId
|
|||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.getActiveTimeline
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
|
|||
|
|
@ -22,17 +22,22 @@ import io.element.android.libraries.di.SingleIn
|
|||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.LiveTimelineProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.getAndUpdate
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import java.io.Closeable
|
||||
import java.util.Optional
|
||||
import javax.inject.Inject
|
||||
|
|
@ -49,12 +54,14 @@ class TimelineController @Inject constructor(
|
|||
private val room: MatrixRoom,
|
||||
) : Closeable, TimelineProvider {
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
||||
|
||||
private val liveTimeline = MutableStateFlow(room.liveTimeline)
|
||||
private val detachedTimeline = MutableStateFlow<Optional<Timeline>>(Optional.empty())
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun timelineItems(): Flow<List<MatrixTimelineItem>> {
|
||||
return currentTimelineFlow().flatMapLatest { it.timelineItems }
|
||||
return currentTimelineFlow.flatMapLatest { it.timelineItems }
|
||||
}
|
||||
|
||||
fun isLive(): Flow<Boolean> {
|
||||
|
|
@ -62,7 +69,7 @@ class TimelineController @Inject constructor(
|
|||
}
|
||||
|
||||
suspend fun invokeOnCurrentTimeline(block: suspend (Timeline.() -> Any)) {
|
||||
currentTimelineFlow().first().run {
|
||||
currentTimelineFlow.value.run {
|
||||
block(this)
|
||||
}
|
||||
}
|
||||
|
|
@ -89,6 +96,10 @@ class TimelineController @Inject constructor(
|
|||
* This does close the detached timeline if any.
|
||||
*/
|
||||
fun focusOnLive() {
|
||||
closeDetachedTimeline()
|
||||
}
|
||||
|
||||
private fun closeDetachedTimeline() {
|
||||
detachedTimeline.getAndUpdate {
|
||||
when {
|
||||
it.isPresent -> {
|
||||
|
|
@ -101,11 +112,12 @@ class TimelineController @Inject constructor(
|
|||
}
|
||||
|
||||
override fun close() {
|
||||
focusOnLive()
|
||||
coroutineScope.cancel()
|
||||
closeDetachedTimeline()
|
||||
}
|
||||
|
||||
suspend fun paginate(direction: Timeline.PaginationDirection): Result<Boolean> {
|
||||
return currentTimelineFlow().first().paginate(direction)
|
||||
return currentTimelineFlow.value.paginate(direction)
|
||||
.onSuccess { hasReachedEnd ->
|
||||
if (direction == Timeline.PaginationDirection.FORWARDS && hasReachedEnd) {
|
||||
focusOnLive()
|
||||
|
|
@ -113,14 +125,14 @@ class TimelineController @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun currentTimelineFlow() = combine(liveTimeline, detachedTimeline) { live, detached ->
|
||||
private val currentTimelineFlow = combine(liveTimeline, detachedTimeline) { live, detached ->
|
||||
when {
|
||||
detached.isPresent -> detached.get()
|
||||
else -> live
|
||||
}
|
||||
}
|
||||
}.stateIn(coroutineScope, SharingStarted.Eagerly, room.liveTimeline)
|
||||
|
||||
override suspend fun getActiveTimeline(): Timeline {
|
||||
return currentTimelineFlow().first()
|
||||
override fun activeTimelineFlow(): StateFlow<Timeline> {
|
||||
return currentTimelineFlow
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ data class TimelineState(
|
|||
val focusedEventId : EventId?,
|
||||
val focusRequestState: FocusRequestState,
|
||||
val eventSink: (TimelineEvents) -> Unit,
|
||||
)
|
||||
){
|
||||
val isTimelineEmpty = timelineItems.none { it is TimelineItem.Event }
|
||||
}
|
||||
|
||||
sealed interface FocusRequestState {
|
||||
data object None : FocusRequestState
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentProvider
|
||||
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
||||
import io.element.android.features.messages.impl.typing.TypingNotificationView
|
||||
import io.element.android.features.messages.impl.typing.aTypingNotificationState
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
|
|
@ -125,12 +126,12 @@ fun TimelineView(
|
|||
reverseLayout = useReverseLayout,
|
||||
contentPadding = PaddingValues(vertical = 8.dp),
|
||||
) {
|
||||
/*
|
||||
item(key = UUID.randomUUID()) {
|
||||
TypingNotificationView(state = typingNotificationState)
|
||||
}
|
||||
|
||||
*/
|
||||
if(state.isLive) {
|
||||
item {
|
||||
TypingNotificationView(state = typingNotificationState)
|
||||
}
|
||||
}
|
||||
items(
|
||||
items = state.timelineItems,
|
||||
contentType = { timelineItem -> timelineItem.contentType() },
|
||||
|
|
@ -165,7 +166,7 @@ fun TimelineView(
|
|||
)
|
||||
|
||||
TimelineScrollHelper(
|
||||
isTimelineEmpty = state.timelineItems.isEmpty(),
|
||||
isTimelineEmpty = state.isTimelineEmpty,
|
||||
lazyListState = lazyListState,
|
||||
forceJumpToBottomVisibility = forceJumpToBottomVisibility,
|
||||
newEventState = state.newEventState,
|
||||
|
|
@ -185,10 +186,6 @@ private fun FocusRequestStateView(
|
|||
onClearFocusRequestState: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BackHandler(enabled = focusRequestState is FocusRequestState.Fetching) {
|
||||
onClearFocusRequestState()
|
||||
}
|
||||
|
||||
when (focusRequestState) {
|
||||
is FocusRequestState.Failure -> {
|
||||
ErrorDialog(
|
||||
|
|
@ -198,7 +195,7 @@ private fun FocusRequestStateView(
|
|||
)
|
||||
}
|
||||
FocusRequestState.Fetching -> {
|
||||
ProgressDialog(modifier = modifier)
|
||||
ProgressDialog(modifier = modifier, onDismissRequest = onClearFocusRequestState)
|
||||
}
|
||||
is FocusRequestState.Cached, FocusRequestState.None -> Unit
|
||||
}
|
||||
|
|
@ -254,7 +251,6 @@ private fun BoxScope.TimelineScrollHelper(
|
|||
}
|
||||
|
||||
LaunchedEffect(canAutoScroll, newEventState) {
|
||||
Timber.d("TimelineScrollHelper - canAutoScroll: $canAutoScroll, newEventState: $newEventState")
|
||||
val shouldScrollToBottom = isScrollFinished && (canAutoScroll || newEventState == NewEventState.FromMe)
|
||||
if (shouldScrollToBottom) {
|
||||
scrollToBottom()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue