TimelineEvents -> TimelineEvent
This commit is contained in:
@@ -36,7 +36,7 @@ import io.element.android.features.messages.impl.attachments.Attachment
|
|||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.TimelinePresenter
|
import io.element.android.features.messages.impl.timeline.TimelinePresenter
|
||||||
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
||||||
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories
|
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories
|
||||||
@@ -151,7 +151,7 @@ class MessagesNode(
|
|||||||
activity: Activity,
|
activity: Activity,
|
||||||
darkTheme: Boolean,
|
darkTheme: Boolean,
|
||||||
url: String,
|
url: String,
|
||||||
eventSink: (TimelineEvents) -> Unit,
|
eventSink: (TimelineEvent) -> Unit,
|
||||||
customTab: Boolean
|
customTab: Boolean
|
||||||
) {
|
) {
|
||||||
when (val permalink = permalinkParser.parse(url)) {
|
when (val permalink = permalinkParser.parse(url)) {
|
||||||
@@ -178,12 +178,12 @@ class MessagesNode(
|
|||||||
|
|
||||||
private fun handleRoomLinkClick(
|
private fun handleRoomLinkClick(
|
||||||
roomLink: PermalinkData.RoomLink,
|
roomLink: PermalinkData.RoomLink,
|
||||||
eventSink: (TimelineEvents) -> Unit,
|
eventSink: (TimelineEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (room.matches(roomLink.roomIdOrAlias)) {
|
if (room.matches(roomLink.roomIdOrAlias)) {
|
||||||
val eventId = roomLink.eventId
|
val eventId = roomLink.eventId
|
||||||
if (eventId != null) {
|
if (eventId != null) {
|
||||||
eventSink(TimelineEvents.FocusOnEvent(eventId))
|
eventSink(TimelineEvent.FocusOnEvent(eventId))
|
||||||
} else {
|
} else {
|
||||||
// Click on the same room, ignore
|
// Click on the same room, ignore
|
||||||
displaySameRoomToast()
|
displaySameRoomToast()
|
||||||
@@ -305,7 +305,7 @@ class MessagesNode(
|
|||||||
}
|
}
|
||||||
LaunchedEffect(focusedEventId) {
|
LaunchedEffect(focusedEventId) {
|
||||||
if (focusedEventId != null) {
|
if (focusedEventId != null) {
|
||||||
state.timelineState.eventSink(TimelineEvents.FocusOnEvent(focusedEventId!!))
|
state.timelineState.eventSink(TimelineEvent.FocusOnEvent(focusedEventId!!))
|
||||||
focusedEventId = null
|
focusedEventId = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer
|
|||||||
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
|
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
|
||||||
import io.element.android.features.messages.impl.timeline.MarkAsFullyRead
|
import io.element.android.features.messages.impl.timeline.MarkAsFullyRead
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineState
|
import io.element.android.features.messages.impl.timeline.TimelineState
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
|
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
|
||||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
|
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
|
||||||
@@ -527,7 +527,7 @@ class MessagesPresenter(
|
|||||||
event: TimelineItem.Event,
|
event: TimelineItem.Event,
|
||||||
timelineState: TimelineState,
|
timelineState: TimelineState,
|
||||||
) {
|
) {
|
||||||
event.eventId?.let { timelineState.eventSink(TimelineEvents.EndPoll(it)) }
|
event.eventId?.let { timelineState.eventSink(TimelineEvent.EndPoll(it)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleCopyLink(event: TimelineItem.Event) {
|
private suspend fun handleCopyLink(event: TimelineItem.Event) {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBan
|
|||||||
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerView
|
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerView
|
||||||
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerViewDefaults
|
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerViewDefaults
|
||||||
import io.element.android.features.messages.impl.timeline.FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS
|
import io.element.android.features.messages.impl.timeline.FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineView
|
import io.element.android.features.messages.impl.timeline.TimelineView
|
||||||
import io.element.android.features.messages.impl.timeline.aGroupedEvents
|
import io.element.android.features.messages.impl.timeline.aGroupedEvents
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemDaySeparator
|
import io.element.android.features.messages.impl.timeline.aTimelineItemDaySeparator
|
||||||
@@ -301,7 +301,7 @@ fun MessagesView(
|
|||||||
state = state,
|
state = state,
|
||||||
onLinkClick = { url, customTab -> onLinkClick(url, customTab) },
|
onLinkClick = { url, customTab -> onLinkClick(url, customTab) },
|
||||||
onRoomSuccessorClick = { roomId ->
|
onRoomSuccessorClick = { roomId ->
|
||||||
state.timelineState.eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(roomId = roomId))
|
state.timelineState.eventSink(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(roomId = roomId))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -371,7 +371,7 @@ fun MessagesView(
|
|||||||
},
|
},
|
||||||
onEmojiReactionClick = ::onEmojiReactionClick,
|
onEmojiReactionClick = ::onEmojiReactionClick,
|
||||||
onVerifiedUserSendFailureClick = { event ->
|
onVerifiedUserSendFailureClick = { event ->
|
||||||
state.timelineState.eventSink(TimelineEvents.ComputeVerifiedUserSendFailure(event))
|
state.timelineState.eventSink(TimelineEvent.ComputeVerifiedUserSendFailure(event))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -489,7 +489,7 @@ private fun MessagesViewContent(
|
|||||||
) {
|
) {
|
||||||
fun focusOnPinnedEvent(eventId: EventId) {
|
fun focusOnPinnedEvent(eventId: EventId) {
|
||||||
state.timelineState.eventSink(
|
state.timelineState.eventSink(
|
||||||
TimelineEvents.FocusOnEvent(eventId = eventId, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds)
|
TimelineEvent.FocusOnEvent(eventId = eventId, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
PinnedMessagesBannerView(
|
PinnedMessagesBannerView(
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import io.element.android.features.messages.impl.attachments.Attachment
|
|||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.TimelinePresenter
|
import io.element.android.features.messages.impl.timeline.TimelinePresenter
|
||||||
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
||||||
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories
|
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories
|
||||||
@@ -148,7 +148,7 @@ class ThreadedMessagesNode(
|
|||||||
activity: Activity,
|
activity: Activity,
|
||||||
darkTheme: Boolean,
|
darkTheme: Boolean,
|
||||||
url: String,
|
url: String,
|
||||||
eventSink: (TimelineEvents) -> Unit,
|
eventSink: (TimelineEvent) -> Unit,
|
||||||
customTab: Boolean
|
customTab: Boolean
|
||||||
) {
|
) {
|
||||||
when (val permalink = permalinkParser.parse(url)) {
|
when (val permalink = permalinkParser.parse(url)) {
|
||||||
@@ -175,12 +175,12 @@ class ThreadedMessagesNode(
|
|||||||
|
|
||||||
private fun handleRoomLinkClick(
|
private fun handleRoomLinkClick(
|
||||||
roomLink: PermalinkData.RoomLink,
|
roomLink: PermalinkData.RoomLink,
|
||||||
eventSink: (TimelineEvents) -> Unit,
|
eventSink: (TimelineEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (room.matches(roomLink.roomIdOrAlias)) {
|
if (room.matches(roomLink.roomIdOrAlias)) {
|
||||||
val eventId = roomLink.eventId
|
val eventId = roomLink.eventId
|
||||||
if (eventId != null) {
|
if (eventId != null) {
|
||||||
eventSink(TimelineEvents.FocusOnEvent(eventId))
|
eventSink(TimelineEvent.FocusOnEvent(eventId))
|
||||||
} else {
|
} else {
|
||||||
// Click on the same room, navigate up
|
// Click on the same room, navigate up
|
||||||
// Note that it can not be enough to go back to the room if the thread has been opened
|
// Note that it can not be enough to go back to the room if the thread has been opened
|
||||||
@@ -277,7 +277,7 @@ class ThreadedMessagesNode(
|
|||||||
}
|
}
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
focusedEventId?.also { eventId ->
|
focusedEventId?.also { eventId ->
|
||||||
state.timelineState.eventSink(TimelineEvents.FocusOnEvent(eventId))
|
state.timelineState.eventSink(TimelineEvent.FocusOnEvent(eventId))
|
||||||
}
|
}
|
||||||
// Reset the focused event id to null to avoid refocusing when restoring node.
|
// Reset the focused event id to null to avoid refocusing when restoring node.
|
||||||
focusedEventId = null
|
focusedEventId = null
|
||||||
|
|||||||
@@ -16,19 +16,19 @@ import io.element.android.libraries.matrix.api.core.ThreadId
|
|||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
sealed interface TimelineEvents {
|
sealed interface TimelineEvent {
|
||||||
data class OnScrollFinished(val firstIndex: Int) : TimelineEvents
|
data class OnScrollFinished(val firstIndex: Int) : TimelineEvent
|
||||||
data class FocusOnEvent(val eventId: EventId, val debounce: Duration = Duration.ZERO) : TimelineEvents
|
data class FocusOnEvent(val eventId: EventId, val debounce: Duration = Duration.ZERO) : TimelineEvent
|
||||||
data object ClearFocusRequestState : TimelineEvents
|
data object ClearFocusRequestState : TimelineEvent
|
||||||
data object OnFocusEventRender : TimelineEvents
|
data object OnFocusEventRender : TimelineEvent
|
||||||
data object JumpToLive : TimelineEvents
|
data object JumpToLive : TimelineEvent
|
||||||
|
|
||||||
data object HideShieldDialog : TimelineEvents
|
data object HideShieldDialog : TimelineEvent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Events coming from a timeline item.
|
* Events coming from a timeline item.
|
||||||
*/
|
*/
|
||||||
sealed interface EventFromTimelineItem : TimelineEvents
|
sealed interface EventFromTimelineItem : TimelineEvent
|
||||||
|
|
||||||
data class ComputeVerifiedUserSendFailure(val event: TimelineItem.Event) : EventFromTimelineItem
|
data class ComputeVerifiedUserSendFailure(val event: TimelineItem.Event) : EventFromTimelineItem
|
||||||
data class ShowShieldDialog(val messageShieldData: MessageShieldData) : EventFromTimelineItem
|
data class ShowShieldDialog(val messageShieldData: MessageShieldData) : EventFromTimelineItem
|
||||||
@@ -43,18 +43,18 @@ sealed interface TimelineEvents {
|
|||||||
/**
|
/**
|
||||||
* Events coming from a poll item.
|
* Events coming from a poll item.
|
||||||
*/
|
*/
|
||||||
sealed interface TimelineItemPollEvents : EventFromTimelineItem
|
sealed interface TimelineItemPollEvent : EventFromTimelineItem
|
||||||
|
|
||||||
data class SelectPollAnswer(
|
data class SelectPollAnswer(
|
||||||
val pollStartId: EventId,
|
val pollStartId: EventId,
|
||||||
val answerId: String
|
val answerId: String
|
||||||
) : TimelineItemPollEvents
|
) : TimelineItemPollEvent
|
||||||
|
|
||||||
data class EndPoll(
|
data class EndPoll(
|
||||||
val pollStartId: EventId,
|
val pollStartId: EventId,
|
||||||
) : TimelineItemPollEvents
|
) : TimelineItemPollEvent
|
||||||
|
|
||||||
data class EditPoll(
|
data class EditPoll(
|
||||||
val pollStartId: EventId,
|
val pollStartId: EventId,
|
||||||
) : TimelineItemPollEvents
|
) : TimelineItemPollEvent
|
||||||
}
|
}
|
||||||
@@ -150,9 +150,9 @@ class TimelinePresenter(
|
|||||||
value = featureFlagService.isFeatureEnabled(FeatureFlags.Threads)
|
value = featureFlagService.isFeatureEnabled(FeatureFlags.Threads)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleEvent(event: TimelineEvents) {
|
fun handleEvent(event: TimelineEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is TimelineEvents.LoadMore -> {
|
is TimelineEvent.LoadMore -> {
|
||||||
if (event.direction == Timeline.PaginationDirection.FORWARDS && timelineMode is Timeline.Mode.Thread) {
|
if (event.direction == Timeline.PaginationDirection.FORWARDS && timelineMode is Timeline.Mode.Thread) {
|
||||||
// Do not paginate forwards in thread mode, as it's not supported
|
// Do not paginate forwards in thread mode, as it's not supported
|
||||||
return
|
return
|
||||||
@@ -161,7 +161,7 @@ class TimelinePresenter(
|
|||||||
timelineController.paginate(direction = event.direction)
|
timelineController.paginate(direction = event.direction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is TimelineEvents.OnScrollFinished -> {
|
is TimelineEvent.OnScrollFinished -> {
|
||||||
if (isLive) {
|
if (isLive) {
|
||||||
if (event.firstIndex == 0) {
|
if (event.firstIndex == 0) {
|
||||||
newEventState.value = NewEventState.None
|
newEventState.value = NewEventState.None
|
||||||
@@ -177,7 +177,7 @@ class TimelinePresenter(
|
|||||||
newEventState.value = NewEventState.None
|
newEventState.value = NewEventState.None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is TimelineEvents.SelectPollAnswer -> sessionCoroutineScope.launch {
|
is TimelineEvent.SelectPollAnswer -> sessionCoroutineScope.launch {
|
||||||
timelineController.invokeOnCurrentTimeline {
|
timelineController.invokeOnCurrentTimeline {
|
||||||
sendPollResponseAction.execute(
|
sendPollResponseAction.execute(
|
||||||
timeline = this,
|
timeline = this,
|
||||||
@@ -186,7 +186,7 @@ class TimelinePresenter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is TimelineEvents.EndPoll -> sessionCoroutineScope.launch {
|
is TimelineEvent.EndPoll -> sessionCoroutineScope.launch {
|
||||||
timelineController.invokeOnCurrentTimeline {
|
timelineController.invokeOnCurrentTimeline {
|
||||||
endPollAction.execute(
|
endPollAction.execute(
|
||||||
timeline = this,
|
timeline = this,
|
||||||
@@ -194,38 +194,38 @@ class TimelinePresenter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is TimelineEvents.EditPoll -> {
|
is TimelineEvent.EditPoll -> {
|
||||||
navigator.navigateToEditPoll(event.pollStartId)
|
navigator.navigateToEditPoll(event.pollStartId)
|
||||||
}
|
}
|
||||||
is TimelineEvents.FocusOnEvent -> sessionCoroutineScope.launch {
|
is TimelineEvent.FocusOnEvent -> sessionCoroutineScope.launch {
|
||||||
focusRequestState.value = FocusRequestState.Requested(event.eventId, event.debounce)
|
focusRequestState.value = FocusRequestState.Requested(event.eventId, event.debounce)
|
||||||
delay(event.debounce)
|
delay(event.debounce)
|
||||||
Timber.tag(tag).d("Started focus on ${event.eventId}")
|
Timber.tag(tag).d("Started focus on ${event.eventId}")
|
||||||
focusOnEvent(event.eventId, focusRequestState)
|
focusOnEvent(event.eventId, focusRequestState)
|
||||||
}.start()
|
}.start()
|
||||||
is TimelineEvents.OnFocusEventRender -> {
|
is TimelineEvent.OnFocusEventRender -> {
|
||||||
// If there was a pending 'notification tap opens timeline' transaction, finish it now we're focused in the required event
|
// If there was a pending 'notification tap opens timeline' transaction, finish it now we're focused in the required event
|
||||||
analyticsService.finishLongRunningTransaction(NotificationToMessage)
|
analyticsService.finishLongRunningTransaction(NotificationToMessage)
|
||||||
|
|
||||||
focusRequestState.value = focusRequestState.value.onFocusEventRender()
|
focusRequestState.value = focusRequestState.value.onFocusEventRender()
|
||||||
}
|
}
|
||||||
is TimelineEvents.ClearFocusRequestState -> {
|
is TimelineEvent.ClearFocusRequestState -> {
|
||||||
focusRequestState.value = FocusRequestState.None
|
focusRequestState.value = FocusRequestState.None
|
||||||
}
|
}
|
||||||
is TimelineEvents.JumpToLive -> {
|
is TimelineEvent.JumpToLive -> {
|
||||||
timelineController.focusOnLive()
|
timelineController.focusOnLive()
|
||||||
}
|
}
|
||||||
TimelineEvents.HideShieldDialog -> messageShieldDialogData.value = null
|
TimelineEvent.HideShieldDialog -> messageShieldDialogData.value = null
|
||||||
is TimelineEvents.ShowShieldDialog -> messageShieldDialogData.value = event.messageShieldData
|
is TimelineEvent.ShowShieldDialog -> messageShieldDialogData.value = event.messageShieldData
|
||||||
is TimelineEvents.ComputeVerifiedUserSendFailure -> {
|
is TimelineEvent.ComputeVerifiedUserSendFailure -> {
|
||||||
resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(event.event))
|
resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(event.event))
|
||||||
}
|
}
|
||||||
is TimelineEvents.NavigateToPredecessorOrSuccessorRoom -> {
|
is TimelineEvent.NavigateToPredecessorOrSuccessorRoom -> {
|
||||||
// Navigate to the predecessor or successor room
|
// Navigate to the predecessor or successor room
|
||||||
val serverNames = calculateServerNamesForRoom(room)
|
val serverNames = calculateServerNamesForRoom(room)
|
||||||
navigator.navigateToRoom(event.roomId, null, serverNames)
|
navigator.navigateToRoom(event.roomId, null, serverNames)
|
||||||
}
|
}
|
||||||
is TimelineEvents.OpenThread -> {
|
is TimelineEvent.OpenThread -> {
|
||||||
navigator.navigateToThread(
|
navigator.navigateToThread(
|
||||||
threadRootId = event.threadRootEventId,
|
threadRootId = event.threadRootEventId,
|
||||||
focusedEventId = event.focusedEvent,
|
focusedEventId = event.focusedEvent,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ data class TimelineState(
|
|||||||
val messageShieldDialogData: MessageShieldData?,
|
val messageShieldDialogData: MessageShieldData?,
|
||||||
val resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState,
|
val resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState,
|
||||||
val displayThreadSummaries: Boolean,
|
val displayThreadSummaries: Boolean,
|
||||||
val eventSink: (TimelineEvents) -> Unit,
|
val eventSink: (TimelineEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
private val lastTimelineEvent = timelineItems.firstOrNull { it is TimelineItem.Event } as? TimelineItem.Event
|
private val lastTimelineEvent = timelineItems.firstOrNull { it is TimelineItem.Event } as? TimelineItem.Event
|
||||||
val hasAnyEvent = lastTimelineEvent != null
|
val hasAnyEvent = lastTimelineEvent != null
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ fun aTimelineState(
|
|||||||
messageShield: MessageShield? = null,
|
messageShield: MessageShield? = null,
|
||||||
resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState = aResolveVerifiedUserSendFailureState(),
|
resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState = aResolveVerifiedUserSendFailureState(),
|
||||||
displayThreadSummaries: Boolean = false,
|
displayThreadSummaries: Boolean = false,
|
||||||
eventSink: (TimelineEvents) -> Unit = {},
|
eventSink: (TimelineEvent) -> Unit = {},
|
||||||
): TimelineState {
|
): TimelineState {
|
||||||
val focusedEventId = timelineItems.filterIsInstance<TimelineItem.Event>().getOrNull(focusedEventIndex)?.eventId
|
val focusedEventId = timelineItems.filterIsInstance<TimelineItem.Event>().getOrNull(focusedEventIndex)?.eventId
|
||||||
val focusRequestState = if (focusedEventId != null) {
|
val focusRequestState = if (focusedEventId != null) {
|
||||||
|
|||||||
@@ -107,19 +107,19 @@ fun TimelineView(
|
|||||||
nestedScrollConnection: NestedScrollConnection = rememberNestedScrollInteropConnection(),
|
nestedScrollConnection: NestedScrollConnection = rememberNestedScrollInteropConnection(),
|
||||||
) {
|
) {
|
||||||
fun clearFocusRequestState() {
|
fun clearFocusRequestState() {
|
||||||
state.eventSink(TimelineEvents.ClearFocusRequestState)
|
state.eventSink(TimelineEvent.ClearFocusRequestState)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onScrollFinishAt(firstVisibleIndex: Int) {
|
fun onScrollFinishAt(firstVisibleIndex: Int) {
|
||||||
state.eventSink(TimelineEvents.OnScrollFinished(firstVisibleIndex))
|
state.eventSink(TimelineEvent.OnScrollFinished(firstVisibleIndex))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onFocusEventRender() {
|
fun onFocusEventRender() {
|
||||||
state.eventSink(TimelineEvents.OnFocusEventRender)
|
state.eventSink(TimelineEvent.OnFocusEventRender)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onJumpToLive() {
|
fun onJumpToLive() {
|
||||||
state.eventSink(TimelineEvents.JumpToLive)
|
state.eventSink(TimelineEvent.JumpToLive)
|
||||||
}
|
}
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -129,7 +129,7 @@ fun TimelineView(
|
|||||||
val useReverseLayout = !isTalkbackActive()
|
val useReverseLayout = !isTalkbackActive()
|
||||||
|
|
||||||
fun inReplyToClick(eventId: EventId) {
|
fun inReplyToClick(eventId: EventId) {
|
||||||
state.eventSink(TimelineEvents.FocusOnEvent(eventId))
|
state.eventSink(TimelineEvent.FocusOnEvent(eventId))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onLinkLongClick(link: Link) {
|
fun onLinkLongClick(link: Link) {
|
||||||
@@ -143,7 +143,7 @@ fun TimelineView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun prefetchMoreItems() {
|
fun prefetchMoreItems() {
|
||||||
state.eventSink(TimelineEvents.LoadMore(Timeline.PaginationDirection.BACKWARDS))
|
state.eventSink(TimelineEvent.LoadMore(Timeline.PaginationDirection.BACKWARDS))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animate alpha when timeline is first displayed, to avoid flashes or glitching when viewing rooms
|
// Animate alpha when timeline is first displayed, to avoid flashes or glitching when viewing rooms
|
||||||
@@ -223,7 +223,7 @@ private fun MessageShieldDialog(state: TimelineState) {
|
|||||||
val messageShield = state.messageShieldDialogData ?: return
|
val messageShield = state.messageShieldDialogData ?: return
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
content = messageShield.toText(),
|
content = messageShield.toText(),
|
||||||
onDismiss = { state.eventSink.invoke(TimelineEvents.HideShieldDialog) },
|
onDismiss = { state.eventSink.invoke(TimelineEvent.HideShieldDialog) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.isEdited
|
import io.element.android.features.messages.impl.timeline.model.event.isEdited
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.isRedacted
|
import io.element.android.features.messages.impl.timeline.model.event.isRedacted
|
||||||
@@ -33,13 +33,12 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.isCritical
|
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineEventTimestampView(
|
fun TimelineEventTimestampView(
|
||||||
event: TimelineItem.Event,
|
event: TimelineItem.Event,
|
||||||
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
eventSink: (TimelineEvent.EventFromTimelineItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val formattedTime = event.sentTime
|
val formattedTime = event.sentTime
|
||||||
@@ -80,7 +79,7 @@ fun TimelineEventTimestampView(
|
|||||||
enabled = isVerifiedUserSendFailure,
|
enabled = isVerifiedUserSendFailure,
|
||||||
onClickLabel = stringResource(CommonStrings.action_open_context_menu),
|
onClickLabel = stringResource(CommonStrings.action_open_context_menu),
|
||||||
) {
|
) {
|
||||||
eventSink(TimelineEvents.ComputeVerifiedUserSendFailure(event))
|
eventSink(TimelineEvent.ComputeVerifiedUserSendFailure(event))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -96,7 +95,7 @@ fun TimelineEventTimestampView(
|
|||||||
.clickable(
|
.clickable(
|
||||||
onClickLabel = stringResource(CommonStrings.a11y_view_details),
|
onClickLabel = stringResource(CommonStrings.a11y_view_details),
|
||||||
) {
|
) {
|
||||||
eventSink(TimelineEvents.ShowShieldDialog(shield))
|
eventSink(TimelineEvent.ShowShieldDialog(shield))
|
||||||
},
|
},
|
||||||
tint = shield.toIconColor(),
|
tint = shield.toIconColor(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ import androidx.constraintlayout.compose.ConstrainScope
|
|||||||
import androidx.constraintlayout.compose.ConstraintLayout
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||||
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
||||||
@@ -153,7 +153,7 @@ fun TimelineItemEventRow(
|
|||||||
onMoreReactionsClick: (eventId: TimelineItem.Event) -> Unit,
|
onMoreReactionsClick: (eventId: TimelineItem.Event) -> Unit,
|
||||||
onReadReceiptClick: (event: TimelineItem.Event) -> Unit,
|
onReadReceiptClick: (event: TimelineItem.Event) -> Unit,
|
||||||
onSwipeToReply: () -> Unit,
|
onSwipeToReply: () -> Unit,
|
||||||
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
eventSink: (TimelineEvent.EventFromTimelineItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
eventContentView: @Composable (Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit = { contentModifier, onContentLayoutChange ->
|
eventContentView: @Composable (Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit = { contentModifier, onContentLayoutChange ->
|
||||||
// Only pass down a custom clickable lambda if the content can be clicked separately
|
// Only pass down a custom clickable lambda if the content can be clicked separately
|
||||||
@@ -278,7 +278,7 @@ fun TimelineItemEventRow(
|
|||||||
isOutgoing = event.isMine,
|
isOutgoing = event.isMine,
|
||||||
onClick = {
|
onClick = {
|
||||||
event.eventId?.let {
|
event.eventId?.let {
|
||||||
eventSink(TimelineEvents.OpenThread(it.toThreadId(), null))
|
eventSink(TimelineEvent.OpenThread(it.toThreadId(), null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -410,7 +410,7 @@ private fun TimelineItemEventRowContent(
|
|||||||
onReactionClick: (emoji: String) -> Unit,
|
onReactionClick: (emoji: String) -> Unit,
|
||||||
onReactionLongClick: (emoji: String) -> Unit,
|
onReactionLongClick: (emoji: String) -> Unit,
|
||||||
onMoreReactionsClick: (event: TimelineItem.Event) -> Unit,
|
onMoreReactionsClick: (event: TimelineItem.Event) -> Unit,
|
||||||
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
eventSink: (TimelineEvent.EventFromTimelineItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
eventContentView: @Composable (Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit,
|
eventContentView: @Composable (Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -585,7 +585,7 @@ private fun MessageEventBubbleContent(
|
|||||||
timelineProtectionState: TimelineProtectionState,
|
timelineProtectionState: TimelineProtectionState,
|
||||||
onMessageLongClick: () -> Unit,
|
onMessageLongClick: () -> Unit,
|
||||||
inReplyToClick: () -> Unit,
|
inReplyToClick: () -> Unit,
|
||||||
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
eventSink: (TimelineEvent.EventFromTimelineItem) -> Unit,
|
||||||
@SuppressLint("ModifierParameter")
|
@SuppressLint("ModifierParameter")
|
||||||
// need to rename this modifier to prevent linter false positives
|
// need to rename this modifier to prevent linter false positives
|
||||||
@Suppress("ModifierNaming")
|
@Suppress("ModifierNaming")
|
||||||
@@ -623,7 +623,7 @@ private fun MessageEventBubbleContent(
|
|||||||
@Composable
|
@Composable
|
||||||
fun WithTimestampLayout(
|
fun WithTimestampLayout(
|
||||||
timestampPosition: TimestampPosition,
|
timestampPosition: TimestampPosition,
|
||||||
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
eventSink: (TimelineEvent.EventFromTimelineItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
canShrinkContent: Boolean = false,
|
canShrinkContent: Boolean = false,
|
||||||
content: @Composable (onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit) -> Unit,
|
content: @Composable (onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit) -> Unit,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
import io.element.android.features.messages.impl.R
|
import io.element.android.features.messages.impl.R
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||||
import io.element.android.features.messages.impl.timeline.aGroupedEvents
|
import io.element.android.features.messages.impl.timeline.aGroupedEvents
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
||||||
@@ -57,7 +57,7 @@ fun TimelineItemGroupedEventsRow(
|
|||||||
onReactionLongClick: (key: String, TimelineItem.Event) -> Unit,
|
onReactionLongClick: (key: String, TimelineItem.Event) -> Unit,
|
||||||
onMoreReactionsClick: (TimelineItem.Event) -> Unit,
|
onMoreReactionsClick: (TimelineItem.Event) -> Unit,
|
||||||
onReadReceiptClick: (TimelineItem.Event) -> Unit,
|
onReadReceiptClick: (TimelineItem.Event) -> Unit,
|
||||||
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
eventSink: (TimelineEvent.EventFromTimelineItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit =
|
eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit =
|
||||||
{ event, contentModifier, onContentLayoutChange ->
|
{ event, contentModifier, onContentLayoutChange ->
|
||||||
@@ -130,7 +130,7 @@ private fun TimelineItemGroupedEventsRowContent(
|
|||||||
onReactionLongClick: (key: String, TimelineItem.Event) -> Unit,
|
onReactionLongClick: (key: String, TimelineItem.Event) -> Unit,
|
||||||
onMoreReactionsClick: (TimelineItem.Event) -> Unit,
|
onMoreReactionsClick: (TimelineItem.Event) -> Unit,
|
||||||
onReadReceiptClick: (TimelineItem.Event) -> Unit,
|
onReadReceiptClick: (TimelineItem.Event) -> Unit,
|
||||||
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
eventSink: (TimelineEvent.EventFromTimelineItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit =
|
eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit =
|
||||||
{ event, contentModifier, onContentLayoutChange ->
|
{ event, contentModifier, onContentLayoutChange ->
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import androidx.compose.ui.semantics.semantics
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||||
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||||
@@ -73,7 +73,7 @@ internal fun TimelineItemRow(
|
|||||||
onReadReceiptClick: (TimelineItem.Event) -> Unit,
|
onReadReceiptClick: (TimelineItem.Event) -> Unit,
|
||||||
onSwipeToReply: (TimelineItem.Event) -> Unit,
|
onSwipeToReply: (TimelineItem.Event) -> Unit,
|
||||||
onJoinCallClick: () -> Unit,
|
onJoinCallClick: () -> Unit,
|
||||||
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
eventSink: (TimelineEvent.EventFromTimelineItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit =
|
eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit =
|
||||||
{ event, contentModifier, onContentLayoutChange ->
|
{ event, contentModifier, onContentLayoutChange ->
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||||
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
||||||
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
|
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
|
||||||
@@ -44,7 +44,7 @@ fun TimelineItemStateEventRow(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
onReadReceiptsClick: (event: TimelineItem.Event) -> Unit,
|
onReadReceiptsClick: (event: TimelineItem.Event) -> Unit,
|
||||||
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
eventSink: (TimelineEvent.EventFromTimelineItem) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
||||||
import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemDaySeparatorView
|
import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemDaySeparatorView
|
||||||
import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemReadMarkerView
|
import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemReadMarkerView
|
||||||
@@ -35,7 +35,7 @@ import timber.log.Timber
|
|||||||
fun TimelineItemVirtualRow(
|
fun TimelineItemVirtualRow(
|
||||||
virtual: TimelineItem.Virtual,
|
virtual: TimelineItem.Virtual,
|
||||||
timelineRoomInfo: TimelineRoomInfo,
|
timelineRoomInfo: TimelineRoomInfo,
|
||||||
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
eventSink: (TimelineEvent.EventFromTimelineItem) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
@@ -48,7 +48,7 @@ fun TimelineItemVirtualRow(
|
|||||||
roomName = timelineRoomInfo.name,
|
roomName = timelineRoomInfo.name,
|
||||||
isDm = timelineRoomInfo.isDm,
|
isDm = timelineRoomInfo.isDm,
|
||||||
onPredecessorRoomClick = { roomId ->
|
onPredecessorRoomClick = { roomId ->
|
||||||
eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(roomId))
|
eventSink(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(roomId))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ fun TimelineItemVirtualRow(
|
|||||||
val latestEventSink by rememberUpdatedState(eventSink)
|
val latestEventSink by rememberUpdatedState(eventSink)
|
||||||
LaunchedEffect(virtual.model.timestamp) {
|
LaunchedEffect(virtual.model.timestamp) {
|
||||||
Timber.d("Pagination triggered by load more indicator")
|
Timber.d("Pagination triggered by load more indicator")
|
||||||
latestEventSink(TimelineEvents.LoadMore(virtual.model.direction))
|
latestEventSink(TimelineEvent.LoadMore(virtual.model.direction))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Empty model trick to avoid timeline jumping during forward pagination.
|
// Empty model trick to avoid timeline jumping during forward pagination.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ package io.element.android.features.messages.impl.timeline.components.event
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||||
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
||||||
import io.element.android.features.messages.impl.timeline.di.rememberPresenter
|
import io.element.android.features.messages.impl.timeline.di.rememberPresenter
|
||||||
@@ -43,7 +43,7 @@ fun TimelineItemEventContentView(
|
|||||||
onShowContentClick: () -> Unit,
|
onShowContentClick: () -> Unit,
|
||||||
onLinkClick: (Link) -> Unit,
|
onLinkClick: (Link) -> Unit,
|
||||||
onLinkLongClick: (Link) -> Unit,
|
onLinkLongClick: (Link) -> Unit,
|
||||||
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit,
|
eventSink: (TimelineEvent.EventFromTimelineItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit = {},
|
onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ package io.element.android.features.messages.impl.timeline.components.event
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContentProvider
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContentProvider
|
||||||
import io.element.android.features.poll.api.pollcontent.PollContentView
|
import io.element.android.features.poll.api.pollcontent.PollContentView
|
||||||
@@ -23,19 +23,19 @@ import kotlinx.collections.immutable.toImmutableList
|
|||||||
@Composable
|
@Composable
|
||||||
fun TimelineItemPollView(
|
fun TimelineItemPollView(
|
||||||
content: TimelineItemPollContent,
|
content: TimelineItemPollContent,
|
||||||
eventSink: (TimelineEvents.TimelineItemPollEvents) -> Unit,
|
eventSink: (TimelineEvent.TimelineItemPollEvent) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
fun onSelectAnswer(pollStartId: EventId, answerId: String) {
|
fun onSelectAnswer(pollStartId: EventId, answerId: String) {
|
||||||
eventSink(TimelineEvents.SelectPollAnswer(pollStartId, answerId))
|
eventSink(TimelineEvent.SelectPollAnswer(pollStartId, answerId))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEndPoll(pollStartId: EventId) {
|
fun onEndPoll(pollStartId: EventId) {
|
||||||
eventSink(TimelineEvents.EndPoll(pollStartId))
|
eventSink(TimelineEvent.EndPoll(pollStartId))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEditPoll(pollStartId: EventId) {
|
fun onEditPoll(pollStartId: EventId) {
|
||||||
eventSink(TimelineEvents.EditPoll(pollStartId))
|
eventSink(TimelineEvent.EditPoll(pollStartId))
|
||||||
}
|
}
|
||||||
|
|
||||||
PollContentView(
|
PollContentView(
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMess
|
|||||||
import io.element.android.features.messages.impl.timeline.FakeMarkAsFullyRead
|
import io.element.android.features.messages.impl.timeline.FakeMarkAsFullyRead
|
||||||
import io.element.android.features.messages.impl.timeline.MarkAsFullyRead
|
import io.element.android.features.messages.impl.timeline.MarkAsFullyRead
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineState
|
import io.element.android.features.messages.impl.timeline.aTimelineState
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
|
||||||
@@ -482,13 +482,13 @@ class MessagesPresenterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - handle action end poll`() = runTest {
|
fun `present - handle action end poll`() = runTest {
|
||||||
val timelineEventSink = EventsRecorder<TimelineEvents>()
|
val timelineEventSink = EventsRecorder<TimelineEvent>()
|
||||||
val presenter = createMessagesPresenter(timelineEventSink = timelineEventSink)
|
val presenter = createMessagesPresenter(timelineEventSink = timelineEventSink)
|
||||||
presenter.testWithLifecycleOwner {
|
presenter.testWithLifecycleOwner {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.EndPoll, aMessageEvent(content = aTimelineItemPollContent())))
|
initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.EndPoll, aMessageEvent(content = aTimelineItemPollContent())))
|
||||||
delay(1)
|
delay(1)
|
||||||
timelineEventSink.assertSingle(TimelineEvents.EndPoll(AN_EVENT_ID))
|
timelineEventSink.assertSingle(TimelineEvent.EndPoll(AN_EVENT_ID))
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1263,7 +1263,7 @@ class MessagesPresenterTest {
|
|||||||
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
||||||
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
|
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
|
||||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||||
timelineEventSink: (TimelineEvents) -> Unit = {},
|
timelineEventSink: (TimelineEvent) -> Unit = {},
|
||||||
permalinkParser: PermalinkParser = FakePermalinkParser(),
|
permalinkParser: PermalinkParser = FakePermalinkParser(),
|
||||||
messageComposerPresenter: Presenter<MessageComposerState> = Presenter {
|
messageComposerPresenter: Presenter<MessageComposerState> = Presenter {
|
||||||
aMessageComposerState(
|
aMessageComposerState(
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import io.element.android.features.messages.impl.messagecomposer.aMessageCompose
|
|||||||
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerItem
|
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerItem
|
||||||
import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMessagesBannerState
|
import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMessagesBannerState
|
||||||
import io.element.android.features.messages.impl.timeline.FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS
|
import io.element.android.features.messages.impl.timeline.FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemList
|
import io.element.android.features.messages.impl.timeline.aTimelineItemList
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemReadReceipts
|
import io.element.android.features.messages.impl.timeline.aTimelineItemReadReceipts
|
||||||
@@ -483,7 +483,7 @@ class MessagesViewTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on verified user send failure from action list emits the expected Event`() {
|
fun `clicking on verified user send failure from action list emits the expected Event`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvents>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
val state = aMessagesState()
|
val state = aMessagesState()
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||||
val stateWithActionListState = state.copy(
|
val stateWithActionListState = state.copy(
|
||||||
@@ -506,7 +506,7 @@ class MessagesViewTest {
|
|||||||
rule.onNodeWithText(verifiedUserSendFailure).performClick()
|
rule.onNodeWithText(verifiedUserSendFailure).performClick()
|
||||||
// Give time for the close animation to complete
|
// Give time for the close animation to complete
|
||||||
rule.mainClock.advanceTimeBy(milliseconds = 1_000)
|
rule.mainClock.advanceTimeBy(milliseconds = 1_000)
|
||||||
eventsRecorder.assertSingle(TimelineEvents.ComputeVerifiedUserSendFailure(timelineItem))
|
eventsRecorder.assertSingle(TimelineEvent.ComputeVerifiedUserSendFailure(timelineItem))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -552,7 +552,7 @@ class MessagesViewTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on pinned messages banner emits the expected Event`() {
|
fun `clicking on pinned messages banner emits the expected Event`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvents>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
timelineState = aTimelineState(eventSink = eventsRecorder),
|
timelineState = aTimelineState(eventSink = eventsRecorder),
|
||||||
pinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(
|
pinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(
|
||||||
@@ -566,12 +566,12 @@ class MessagesViewTest {
|
|||||||
)
|
)
|
||||||
rule.setMessagesView(state = state)
|
rule.setMessagesView(state = state)
|
||||||
rule.onNodeWithText("This is a pinned message").performClick()
|
rule.onNodeWithText("This is a pinned message").performClick()
|
||||||
eventsRecorder.assertSingle(TimelineEvents.FocusOnEvent(AN_EVENT_ID, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds))
|
eventsRecorder.assertSingle(TimelineEvent.FocusOnEvent(AN_EVENT_ID, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on successor room button emits expected event`() {
|
fun `clicking on successor room button emits expected event`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvents>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
val successorRoomId = RoomId("!successor:server.org")
|
val successorRoomId = RoomId("!successor:server.org")
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
successorRoom = SuccessorRoom(
|
successorRoom = SuccessorRoom(
|
||||||
@@ -584,7 +584,7 @@ class MessagesViewTest {
|
|||||||
val text = rule.activity.getString(R.string.screen_room_timeline_tombstoned_room_action)
|
val text = rule.activity.getString(R.string.screen_room_timeline_tombstoned_room_action)
|
||||||
// The bottomsheet subcompose seems to make the node to appear twice
|
// The bottomsheet subcompose seems to make the node to appear twice
|
||||||
rule.onAllNodesWithText(text).onFirst().performClick()
|
rule.onAllNodesWithText(text).onFirst().performClick()
|
||||||
eventsRecorder.assertSingle(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(successorRoomId))
|
eventsRecorder.assertSingle(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(successorRoomId))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
package io.element.android.features.messages.impl.timeline
|
package io.element.android.features.messages.impl.timeline
|
||||||
|
|
||||||
import app.cash.turbine.ReceiveTurbine
|
import app.cash.turbine.ReceiveTurbine
|
||||||
import app.cash.turbine.test
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.messages.impl.FakeMessagesNavigator
|
import io.element.android.features.messages.impl.FakeMessagesNavigator
|
||||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.aResolveVerifiedUserSendFailureState
|
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.aResolveVerifiedUserSendFailureState
|
||||||
@@ -120,8 +119,8 @@ class TimelinePresenterTest {
|
|||||||
val presenter = createTimelinePresenter(timeline = timeline)
|
val presenter = createTimelinePresenter(timeline = timeline)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(TimelineEvents.LoadMore(Timeline.PaginationDirection.BACKWARDS))
|
initialState.eventSink.invoke(TimelineEvent.LoadMore(Timeline.PaginationDirection.BACKWARDS))
|
||||||
initialState.eventSink.invoke(TimelineEvents.LoadMore(Timeline.PaginationDirection.FORWARDS))
|
initialState.eventSink.invoke(TimelineEvent.LoadMore(Timeline.PaginationDirection.FORWARDS))
|
||||||
assert(paginateLambda)
|
assert(paginateLambda)
|
||||||
.isCalledExactly(2)
|
.isCalledExactly(2)
|
||||||
.withSequence(
|
.withSequence(
|
||||||
@@ -173,7 +172,7 @@ class TimelinePresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0))
|
initialState.eventSink.invoke(TimelineEvent.OnScrollFinished(0))
|
||||||
runCurrent()
|
runCurrent()
|
||||||
assert(markAsReadResult)
|
assert(markAsReadResult)
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
@@ -207,7 +206,7 @@ class TimelinePresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
eventSink.invoke(TimelineEvents.OnScrollFinished(1))
|
eventSink.invoke(TimelineEvent.OnScrollFinished(1))
|
||||||
}
|
}
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
assert(sendReadReceiptsLambda)
|
assert(sendReadReceiptsLambda)
|
||||||
@@ -246,8 +245,8 @@ class TimelinePresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
eventSink.invoke(TimelineEvents.OnScrollFinished(0))
|
eventSink.invoke(TimelineEvent.OnScrollFinished(0))
|
||||||
eventSink.invoke(TimelineEvents.OnScrollFinished(1))
|
eventSink.invoke(TimelineEvent.OnScrollFinished(1))
|
||||||
}
|
}
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
assert(sendReadReceiptsLambda)
|
assert(sendReadReceiptsLambda)
|
||||||
@@ -282,8 +281,8 @@ class TimelinePresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
eventSink.invoke(TimelineEvents.OnScrollFinished(1))
|
eventSink.invoke(TimelineEvent.OnScrollFinished(1))
|
||||||
eventSink.invoke(TimelineEvents.OnScrollFinished(1))
|
eventSink.invoke(TimelineEvent.OnScrollFinished(1))
|
||||||
}
|
}
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
@@ -310,7 +309,7 @@ class TimelinePresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1))
|
initialState.eventSink.invoke(TimelineEvent.OnScrollFinished(1))
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
assert(sendReadReceiptsLambda).isNeverCalled()
|
assert(sendReadReceiptsLambda).isNeverCalled()
|
||||||
}
|
}
|
||||||
@@ -349,7 +348,7 @@ class TimelinePresenterTest {
|
|||||||
consumeItemsUntilPredicate { it.timelineItems.size == 3 }
|
consumeItemsUntilPredicate { it.timelineItems.size == 3 }
|
||||||
|
|
||||||
// Scroll to bottom to clear previous FromMe
|
// Scroll to bottom to clear previous FromMe
|
||||||
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0))
|
initialState.eventSink.invoke(TimelineEvent.OnScrollFinished(0))
|
||||||
awaitLastSequentialItem().also { state ->
|
awaitLastSequentialItem().also { state ->
|
||||||
assertThat(state.newEventState).isEqualTo(NewEventState.None)
|
assertThat(state.newEventState).isEqualTo(NewEventState.None)
|
||||||
}
|
}
|
||||||
@@ -429,7 +428,7 @@ class TimelinePresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink.invoke(TimelineEvents.SelectPollAnswer(AN_EVENT_ID, "anAnswerId"))
|
initialState.eventSink.invoke(TimelineEvent.SelectPollAnswer(AN_EVENT_ID, "anAnswerId"))
|
||||||
}
|
}
|
||||||
delay(1)
|
delay(1)
|
||||||
sendPollResponseAction.verifyExecutionCount(1)
|
sendPollResponseAction.verifyExecutionCount(1)
|
||||||
@@ -443,7 +442,7 @@ class TimelinePresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink.invoke(TimelineEvents.EndPoll(AN_EVENT_ID))
|
initialState.eventSink.invoke(TimelineEvent.EndPoll(AN_EVENT_ID))
|
||||||
}
|
}
|
||||||
delay(1)
|
delay(1)
|
||||||
endPollAction.verifyExecutionCount(1)
|
endPollAction.verifyExecutionCount(1)
|
||||||
@@ -459,7 +458,7 @@ class TimelinePresenterTest {
|
|||||||
messagesNavigator = navigator,
|
messagesNavigator = navigator,
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
awaitFirstItem().eventSink(TimelineEvents.EditPoll(AN_EVENT_ID))
|
awaitFirstItem().eventSink(TimelineEvent.EditPoll(AN_EVENT_ID))
|
||||||
onEditPollClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID))
|
onEditPollClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -510,7 +509,7 @@ class TimelinePresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID))
|
initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID))
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
||||||
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Requested(AN_EVENT_ID, Duration.ZERO))
|
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Requested(AN_EVENT_ID, Duration.ZERO))
|
||||||
@@ -524,7 +523,7 @@ class TimelinePresenterTest {
|
|||||||
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Success(AN_EVENT_ID))
|
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Success(AN_EVENT_ID))
|
||||||
assertThat(state.timelineItems).isNotEmpty()
|
assertThat(state.timelineItems).isNotEmpty()
|
||||||
}
|
}
|
||||||
initialState.eventSink.invoke(TimelineEvents.JumpToLive)
|
initialState.eventSink.invoke(TimelineEvent.JumpToLive)
|
||||||
skipItems(2)
|
skipItems(2)
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
// Event stays focused
|
// Event stays focused
|
||||||
@@ -564,7 +563,7 @@ class TimelinePresenterTest {
|
|||||||
// Pre-populate the indexer after the first items have been retrieved
|
// Pre-populate the indexer after the first items have been retrieved
|
||||||
timelineItemIndexer.process(listOf(aMessageEvent(eventId = AN_EVENT_ID)))
|
timelineItemIndexer.process(listOf(aMessageEvent(eventId = AN_EVENT_ID)))
|
||||||
|
|
||||||
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID))
|
initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID))
|
||||||
|
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
@@ -595,7 +594,7 @@ class TimelinePresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink(TimelineEvents.FocusOnEvent(AN_EVENT_ID))
|
initialState.eventSink(TimelineEvent.FocusOnEvent(AN_EVENT_ID))
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
||||||
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Requested(AN_EVENT_ID, Duration.ZERO))
|
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Requested(AN_EVENT_ID, Duration.ZERO))
|
||||||
@@ -606,7 +605,7 @@ class TimelinePresenterTest {
|
|||||||
}
|
}
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.focusRequestState).isInstanceOf(FocusRequestState.Failure::class.java)
|
assertThat(state.focusRequestState).isInstanceOf(FocusRequestState.Failure::class.java)
|
||||||
state.eventSink(TimelineEvents.ClearFocusRequestState)
|
state.eventSink(TimelineEvent.ClearFocusRequestState)
|
||||||
}
|
}
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.None)
|
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.None)
|
||||||
@@ -648,7 +647,7 @@ class TimelinePresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID))
|
initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID))
|
||||||
|
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
||||||
@@ -707,7 +706,7 @@ class TimelinePresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID))
|
initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID))
|
||||||
|
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
||||||
@@ -762,7 +761,7 @@ class TimelinePresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID))
|
initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID))
|
||||||
|
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
||||||
@@ -821,7 +820,7 @@ class TimelinePresenterTest {
|
|||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID))
|
initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID))
|
||||||
|
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
|
||||||
@@ -854,10 +853,10 @@ class TimelinePresenterTest {
|
|||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
assertThat(initialState.messageShieldDialogData).isNull()
|
assertThat(initialState.messageShieldDialogData).isNull()
|
||||||
val shieldData = MessageShieldData(shield, null, null)
|
val shieldData = MessageShieldData(shield, null, null)
|
||||||
initialState.eventSink(TimelineEvents.ShowShieldDialog(shieldData))
|
initialState.eventSink(TimelineEvent.ShowShieldDialog(shieldData))
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.messageShieldDialogData).isEqualTo(shieldData)
|
assertThat(state.messageShieldDialogData).isEqualTo(shieldData)
|
||||||
state.eventSink(TimelineEvents.HideShieldDialog)
|
state.eventSink(TimelineEvent.HideShieldDialog)
|
||||||
}
|
}
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.messageShieldDialogData).isNull()
|
assertThat(state.messageShieldDialogData).isNull()
|
||||||
@@ -963,7 +962,7 @@ class TimelinePresenterTest {
|
|||||||
val presenter = createTimelinePresenter(room = room, messagesNavigator = navigator)
|
val presenter = createTimelinePresenter(room = room, messagesNavigator = navigator)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
initialState.eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(A_ROOM_ID))
|
initialState.eventSink(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(A_ROOM_ID))
|
||||||
assert(onNavigateToRoomLambda)
|
assert(onNavigateToRoomLambda)
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(
|
.with(
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class TimelineViewTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `reaching the end of the timeline with more events to load emits a LoadMore event`() {
|
fun `reaching the end of the timeline with more events to load emits a LoadMore event`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvents>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
rule.setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
timelineItems = persistentListOf<TimelineItem>(
|
timelineItems = persistentListOf<TimelineItem>(
|
||||||
@@ -62,12 +62,12 @@ class TimelineViewTest {
|
|||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
eventsRecorder.assertSingle(TimelineEvents.LoadMore(Timeline.PaginationDirection.BACKWARDS))
|
eventsRecorder.assertSingle(TimelineEvent.LoadMore(Timeline.PaginationDirection.BACKWARDS))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `reaching the end of the timeline does not send a LoadMore event`() {
|
fun `reaching the end of the timeline does not send a LoadMore event`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<TimelineEvent>(expectEvents = false)
|
||||||
rule.setTimelineView(
|
rule.setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
@@ -77,7 +77,7 @@ class TimelineViewTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `scroll to bottom on live timeline does not emit the Event`() {
|
fun `scroll to bottom on live timeline does not emit the Event`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<TimelineEvent>(expectEvents = false)
|
||||||
rule.setTimelineView(
|
rule.setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
isLive = true,
|
isLive = true,
|
||||||
@@ -91,7 +91,7 @@ class TimelineViewTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `scroll to bottom on detached timeline emits the expected Event`() {
|
fun `scroll to bottom on detached timeline emits the expected Event`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvents>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
rule.setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
isLive = false,
|
isLive = false,
|
||||||
@@ -100,12 +100,12 @@ class TimelineViewTest {
|
|||||||
)
|
)
|
||||||
val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom)
|
val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom)
|
||||||
rule.onNodeWithContentDescription(contentDescription).performClick()
|
rule.onNodeWithContentDescription(contentDescription).performClick()
|
||||||
eventsRecorder.assertSingle(TimelineEvents.JumpToLive)
|
eventsRecorder.assertSingle(TimelineEvent.JumpToLive)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `show shield dialog`() {
|
fun `show shield dialog`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvents>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
rule.setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
timelineItems = persistentListOf<TimelineItem>(
|
timelineItems = persistentListOf<TimelineItem>(
|
||||||
@@ -122,15 +122,15 @@ class TimelineViewTest {
|
|||||||
rule.onNodeWithContentDescription(contentDescription).performClick()
|
rule.onNodeWithContentDescription(contentDescription).performClick()
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
TimelineEvents.OnScrollFinished(0),
|
TimelineEvent.OnScrollFinished(0),
|
||||||
TimelineEvents.ShowShieldDialog(MessageShieldData(MessageShield.UnverifiedIdentity(true))),
|
TimelineEvent.ShowShieldDialog(MessageShieldData(MessageShield.UnverifiedIdentity(true))),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `hide shield dialog`() {
|
fun `hide shield dialog`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvents>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
rule.setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
isLive = false,
|
isLive = false,
|
||||||
@@ -139,12 +139,12 @@ class TimelineViewTest {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
rule.clickOn(CommonStrings.action_ok)
|
||||||
eventsRecorder.assertSingle(TimelineEvents.HideShieldDialog)
|
eventsRecorder.assertSingle(TimelineEvent.HideShieldDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `scrolling near to the start of the loaded items triggers a pre-fetch`() {
|
fun `scrolling near to the start of the loaded items triggers a pre-fetch`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvents>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
val items = List<TimelineItem>(200) {
|
val items = List<TimelineItem>(200) {
|
||||||
aTimelineItemEvent(
|
aTimelineItemEvent(
|
||||||
eventId = EventId("\$event_$it"),
|
eventId = EventId("\$event_$it"),
|
||||||
@@ -167,8 +167,8 @@ class TimelineViewTest {
|
|||||||
|
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
TimelineEvents.OnScrollFinished(firstIndex = 0),
|
TimelineEvent.OnScrollFinished(firstIndex = 0),
|
||||||
TimelineEvents.LoadMore(Timeline.PaginationDirection.BACKWARDS),
|
TimelineEvent.LoadMore(Timeline.PaginationDirection.BACKWARDS),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import androidx.compose.ui.test.hasText
|
|||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
@@ -39,7 +39,7 @@ class TimelineItemPollViewTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun testAnswer(answerIndex: Int) {
|
private fun testAnswer(answerIndex: Int) {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvents.TimelineItemPollEvents>()
|
val eventsRecorder = EventsRecorder<TimelineEvent.TimelineItemPollEvent>()
|
||||||
val content = aTimelineItemPollContent()
|
val content = aTimelineItemPollContent()
|
||||||
rule.setContent {
|
rule.setContent {
|
||||||
TimelineItemPollView(
|
TimelineItemPollView(
|
||||||
@@ -52,12 +52,12 @@ class TimelineItemPollViewTest {
|
|||||||
matcher = hasText(answer.text),
|
matcher = hasText(answer.text),
|
||||||
useUnmergedTree = true,
|
useUnmergedTree = true,
|
||||||
).performClick()
|
).performClick()
|
||||||
eventsRecorder.assertSingle(TimelineEvents.SelectPollAnswer(content.eventId!!, answer.id))
|
eventsRecorder.assertSingle(TimelineEvent.SelectPollAnswer(content.eventId!!, answer.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `editing a poll should emit a PollEditClicked event`() {
|
fun `editing a poll should emit a PollEditClicked event`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvents.TimelineItemPollEvents>()
|
val eventsRecorder = EventsRecorder<TimelineEvent.TimelineItemPollEvent>()
|
||||||
val content = aTimelineItemPollContent(
|
val content = aTimelineItemPollContent(
|
||||||
isMine = true,
|
isMine = true,
|
||||||
isEditable = true,
|
isEditable = true,
|
||||||
@@ -69,12 +69,12 @@ class TimelineItemPollViewTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
rule.clickOn(CommonStrings.action_edit_poll)
|
rule.clickOn(CommonStrings.action_edit_poll)
|
||||||
eventsRecorder.assertSingle(TimelineEvents.EditPoll(content.eventId!!))
|
eventsRecorder.assertSingle(TimelineEvent.EditPoll(content.eventId!!))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `closing a poll should emit a PollEndClicked event`() {
|
fun `closing a poll should emit a PollEndClicked event`() {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvents.TimelineItemPollEvents>()
|
val eventsRecorder = EventsRecorder<TimelineEvent.TimelineItemPollEvent>()
|
||||||
val content = aTimelineItemPollContent(
|
val content = aTimelineItemPollContent(
|
||||||
isMine = true,
|
isMine = true,
|
||||||
)
|
)
|
||||||
@@ -88,6 +88,6 @@ class TimelineItemPollViewTest {
|
|||||||
// A confirmation dialog should be shown
|
// A confirmation dialog should be shown
|
||||||
eventsRecorder.assertEmpty()
|
eventsRecorder.assertEmpty()
|
||||||
rule.pressTag(TestTags.dialogPositive.value)
|
rule.pressTag(TestTags.dialogPositive.value)
|
||||||
eventsRecorder.assertSingle(TimelineEvents.EndPoll(content.eventId!!))
|
eventsRecorder.assertSingle(TimelineEvent.EndPoll(content.eventId!!))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user