TimelineEvents -> TimelineEvent

This commit is contained in:
Benoit Marty
2026-01-27 09:45:10 +01:00
parent a1e5afcdd5
commit 6bb979a833
22 changed files with 137 additions and 139 deletions

View File

@@ -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
} }
} }

View File

@@ -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) {

View File

@@ -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(

View File

@@ -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

View File

@@ -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
} }

View File

@@ -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,

View File

@@ -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

View File

@@ -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) {

View File

@@ -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) },
) )
} }

View File

@@ -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(),
) )

View File

@@ -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,

View File

@@ -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 ->

View File

@@ -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 ->

View File

@@ -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() }

View File

@@ -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.

View File

@@ -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 = {},
) { ) {

View File

@@ -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(

View File

@@ -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(

View File

@@ -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

View File

@@ -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(

View File

@@ -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),
) )
) )
} }

View File

@@ -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!!))
} }
} }