diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt index 30f9aade79..e427646a71 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt @@ -22,4 +22,8 @@ sealed interface TimelineEvents { data object LoadMore : TimelineEvents data class SetHighlightedEvent(val eventId: EventId?) : TimelineEvents data class OnScrollFinished(val firstIndex: Int) : TimelineEvents + data class PollAnswerSelected( + val pollStartId: EventId, + val answerId: String + ) : TimelineEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index c53d8fc279..402c332855 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -87,6 +87,13 @@ class TimelinePresenter @Inject constructor( lastReadReceiptId = lastReadReceiptId ) } + is TimelineEvents.PollAnswerSelected -> appScope.launch { + room.sendPollResponse( + pollStartId = event.pollStartId, + answers = listOf(event.answerId), + ) + // TODO Polls: Send poll vote analytic + } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 6e16a3b92d..ff90e8d29a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -100,6 +100,9 @@ fun TimelineView( // TODO implement this logic once we have support to 'jump to event X' in sliding sync } + fun onPollAnswerSelected(pollStartId: EventId, answerId: String) { + state.eventSink(TimelineEvents.PollAnswerSelected(pollStartId, answerId)) + } Box(modifier = modifier) { LazyColumn( @@ -125,6 +128,7 @@ fun TimelineView( onReactionLongClick = onReactionLongClicked, onMoreReactionsClick = onMoreReactionsClicked, onTimestampClicked = onTimestampClicked, + onPollAnswerSelected = ::onPollAnswerSelected, onSwipeToReply = onSwipeToReply, ) } @@ -162,6 +166,7 @@ fun TimelineItemRow( onMoreReactionsClick: (TimelineItem.Event) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, onSwipeToReply: (TimelineItem.Event) -> Unit, + onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, modifier: Modifier = Modifier ) { when (timelineItem) { @@ -194,6 +199,7 @@ fun TimelineItemRow( onMoreReactionsClick = onMoreReactionsClick, onTimestampClicked = onTimestampClicked, onSwipeToReply = { onSwipeToReply(timelineItem) }, + onPollAnswerSelected = onPollAnswerSelected, modifier = modifier, ) } @@ -231,6 +237,7 @@ fun TimelineItemRow( onReactionClick = onReactionClick, onReactionLongClick = onReactionLongClick, onMoreReactionsClick = onMoreReactionsClick, + onPollAnswerSelected = onPollAnswerSelected, onSwipeToReply = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index dd5a2df2e0..10671ee00f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -118,6 +118,7 @@ fun TimelineItemEventRow( onReactionLongClick: (emoji: String, eventId: TimelineItem.Event) -> Unit, onMoreReactionsClick: (eventId: TimelineItem.Event) -> Unit, onSwipeToReply: () -> Unit, + onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, modifier: Modifier = Modifier ) { val coroutineScope = rememberCoroutineScope() @@ -175,6 +176,7 @@ fun TimelineItemEventRow( onReactionClicked = { emoji -> onReactionClick(emoji, event) }, onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) }, onMoreReactionsClicked = { onMoreReactionsClick(event) }, + onPollAnswerSelected = onPollAnswerSelected, ) } } @@ -191,6 +193,7 @@ fun TimelineItemEventRow( onReactionClicked = { emoji -> onReactionClick(emoji, event) }, onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) }, onMoreReactionsClicked = { onMoreReactionsClick(event) }, + onPollAnswerSelected = onPollAnswerSelected, ) } } @@ -232,6 +235,7 @@ private fun TimelineItemEventRowContent( onReactionClicked: (emoji: String) -> Unit, onReactionLongClicked: (emoji: String) -> Unit, onMoreReactionsClicked: (event: TimelineItem.Event) -> Unit, + onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, modifier: Modifier = Modifier, ) { fun ConstrainScope.linkStartOrEnd(event: TimelineItem.Event) = if (event.isMine) { @@ -289,7 +293,8 @@ private fun TimelineItemEventRowContent( inReplyToClick = inReplyToClicked, onTimestampClicked = { onTimestampClicked(event) - } + }, + onPollAnswerSelected = onPollAnswerSelected, ) } @@ -360,6 +365,7 @@ private fun MessageEventBubbleContent( onMessageLongClick: () -> Unit, inReplyToClick: () -> Unit, onTimestampClicked: () -> Unit, + onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, @SuppressLint("ModifierParameter") bubbleModifier: Modifier = Modifier, // need to rename this modifier to distinguish it from the following ones ) { val timestampPosition = when (event.content) { @@ -385,6 +391,7 @@ private fun MessageEventBubbleContent( onClick = onMessageClick, onLongClick = onMessageLongClick, extraPadding = event.toExtraPadding(), + onPollAnswerSelected = onPollAnswerSelected, modifier = modifier, ) } @@ -607,6 +614,7 @@ private fun ContentToPreview() { onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, + onPollAnswerSelected = { _, _ -> }, ) TimelineItemEventRow( event = aTimelineItemEvent( @@ -627,6 +635,7 @@ private fun ContentToPreview() { onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, + onPollAnswerSelected = { _, _ -> }, ) } } @@ -674,6 +683,7 @@ private fun ContentToPreviewWithReply() { onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, + onPollAnswerSelected = { _, _ -> }, ) TimelineItemEventRow( event = aTimelineItemEvent( @@ -695,6 +705,7 @@ private fun ContentToPreviewWithReply() { onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, + onPollAnswerSelected = { _, _ -> }, ) } } @@ -752,6 +763,7 @@ private fun ContentTimestampToPreview(event: TimelineItem.Event) { onMoreReactionsClick = {}, onTimestampClicked = {}, onSwipeToReply = {}, + onPollAnswerSelected = { _, _ -> }, ) } } @@ -792,6 +804,7 @@ private fun ContentWithManyReactionsToPreview() { onMoreReactionsClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, + onPollAnswerSelected = { _, _ -> }, ) } } @@ -816,6 +829,7 @@ internal fun TimelineItemEventRowLongSenderNamePreview() = ElementPreviewLight { onMoreReactionsClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, + onPollAnswerSelected = { _, _ -> }, ) } @@ -836,5 +850,6 @@ internal fun TimelineItemEventTimestampBelowPreview() = ElementPreviewLight { onMoreReactionsClick = {}, onSwipeToReply = {}, onTimestampClicked = {}, + onPollAnswerSelected = { _, _ -> }, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt index d22182d098..b976d24a25 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt @@ -70,6 +70,7 @@ fun TimelineItemStateEventRow( onClick = onClick, onLongClick = onLongClick, extraPadding = noExtraPadding, + onPollAnswerSelected = { _, _ -> error("Polls are not supported in state events") }, modifier = Modifier.defaultTimelineContentPadding() ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt index d53e3f1e5b..e882950d6c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt @@ -31,6 +31,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent +import io.element.android.libraries.matrix.api.core.EventId @Composable fun TimelineItemEventContentView( @@ -39,6 +40,7 @@ fun TimelineItemEventContentView( extraPadding: ExtraPadding, onClick: () -> Unit, onLongClick: () -> Unit, + onPollAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, modifier: Modifier = Modifier ) { when (content) { @@ -93,7 +95,7 @@ fun TimelineItemEventContentView( ) is TimelineItemPollContent -> TimelineItemPollView( content = content, - onAnswerSelected = {}, + onAnswerSelected = onPollAnswerSelected, modifier = modifier, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt index 3608593cde..ec784ad331 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt @@ -24,16 +24,17 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.poll.api.PollContentView import io.element.android.libraries.designsystem.preview.DayNightPreviews import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.matrix.api.poll.PollAnswer +import io.element.android.libraries.matrix.api.core.EventId import kotlinx.collections.immutable.toImmutableList @Composable fun TimelineItemPollView( content: TimelineItemPollContent, - onAnswerSelected: (PollAnswer) -> Unit, + onAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, modifier: Modifier = Modifier, ) { PollContentView( + eventId = content.eventId, question = content.question, answerItems = content.answerItems.toImmutableList(), pollKind = content.pollKind, @@ -49,6 +50,6 @@ internal fun TimelineItemPollViewPreview(@PreviewParameter(TimelineItemPollConte ElementPreview { TimelineItemPollView( content = content, - onAnswerSelected = {}, + onAnswerSelected = { _, _ -> }, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt index 1de4ee7c86..6a74fd11a5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt @@ -55,7 +55,7 @@ class TimelineItemContentFactory @Inject constructor( is RoomMembershipContent -> roomMembershipFactory.create(eventTimelineItem) is StateContent -> stateFactory.create(eventTimelineItem) is StickerContent -> stickerFactory.create(itemContent) - is PollContent -> pollFactory.create(itemContent) + is PollContent -> pollFactory.create(itemContent, eventTimelineItem.eventId) is UnableToDecryptContent -> utdFactory.create(itemContent) is UnknownContent -> TimelineItemUnknownContent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt index 9c06b17056..04551f7086 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt @@ -23,6 +23,7 @@ import io.element.android.features.poll.api.PollAnswerItem import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.poll.isDisclosed import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import javax.inject.Inject @@ -32,7 +33,10 @@ class TimelineItemContentPollFactory @Inject constructor( private val featureFlagService: FeatureFlagService, ) { - suspend fun create(content: PollContent): TimelineItemEventContent { + suspend fun create( + content: PollContent, + eventId: EventId? + ): TimelineItemEventContent { if (!featureFlagService.isFeatureEnabled(FeatureFlags.Polls)) return TimelineItemUnknownContent // Todo Move this computation to the matrix rust sdk @@ -67,6 +71,7 @@ class TimelineItemContentPollFactory @Inject constructor( } return TimelineItemPollContent( + eventId = eventId, question = content.question, answerItems = answerItems, pollKind = content.kind, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt index 0f94b97776..dc47c12a86 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt @@ -17,9 +17,11 @@ package io.element.android.features.messages.impl.timeline.model.event import io.element.android.features.poll.api.PollAnswerItem +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.poll.PollKind data class TimelineItemPollContent( + val eventId: EventId?, val question: String, val answerItems: List, val pollKind: PollKind, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt index 247d450ae7..c475f6dfbd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt @@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.model.event import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.poll.api.aPollAnswerItemList +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.poll.PollKind open class TimelineItemPollContentProvider : PreviewParameterProvider { @@ -30,6 +31,7 @@ open class TimelineItemPollContentProvider : PreviewParameterProvider context.getString(CommonStrings.common_shared_location) is TimelineItemEncryptedContent -> context.getString(CommonStrings.common_unable_to_decrypt) is TimelineItemRedactedContent -> context.getString(CommonStrings.common_message_removed) - is TimelineItemPollContent, // Todo Polls: handle summary + is TimelineItemPollContent -> event.content.question is TimelineItemUnknownContent -> context.getString(CommonStrings.common_unsupported_event) is TimelineItemImageContent -> context.getString(CommonStrings.common_image) is TimelineItemVideoContent -> context.getString(CommonStrings.common_video) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt index b3c805d32d..111b3a370d 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt @@ -32,6 +32,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent import io.element.android.features.poll.api.PollAnswerItem +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.poll.PollAnswer import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.test.A_MESSAGE @@ -384,6 +385,7 @@ class ActionListPresenterTest { val messageEvent = aMessageEvent( isMine = true, content = TimelineItemPollContent( + eventId = EventId("\$anEventId"), question = "Some question?", answerItems = listOf( PollAnswerItem( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt index b4bb14f672..860f8def37 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt @@ -25,7 +25,7 @@ import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.model.TimelineItem -import io.element.android.libraries.matrix.ui.components.aMatrixUserList +import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction @@ -36,8 +36,10 @@ import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aMessageContent import io.element.android.libraries.matrix.test.room.anEventTimelineItem import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline +import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.tests.testutils.awaitWithLatch import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test @@ -248,6 +250,23 @@ class TimelinePresenterTest { } } + @Test + fun `present - PollAnswerSelected event calls into rust room api and analytics`() = runTest { + val room = FakeMatrixRoom() + val presenter = createTimelinePresenter(room) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(TimelineEvents.PollAnswerSelected(AN_EVENT_ID, "anAnswerId")) + } + delay(1) + assertThat(room.sendPollResponseInvocations.size).isEqualTo(1) + assertThat(room.sendPollResponseInvocations.first().answers).isEqualTo(listOf("anAnswerId")) + assertThat(room.sendPollResponseInvocations.first().pollStartId).isEqualTo(AN_EVENT_ID) + // TODO Polls: Test poll vote analytic + } + private fun TestScope.createTimelinePresenter( timeline: MatrixTimeline = FakeMatrixTimeline(), timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory() @@ -259,4 +278,15 @@ class TimelinePresenterTest { appScope = this ) } + + private fun TestScope.createTimelinePresenter( + room: MatrixRoom, + ): TimelinePresenter { + return TimelinePresenter( + timelineItemsFactory = aTimelineItemsFactory(), + room = room, + dispatchers = testCoroutineDispatchers(), + appScope = this + ) + } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/factories/event/TimelineItemContentPollFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/factories/event/TimelineItemContentPollFactoryTest.kt index a1411293e9..8cf5704bba 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/factories/event/TimelineItemContentPollFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/factories/event/TimelineItemContentPollFactoryTest.kt @@ -22,10 +22,12 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.poll.api.PollAnswerItem import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.poll.PollAnswer import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.timeline.item.event.PollContent +import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID_10 import io.element.android.libraries.matrix.test.A_USER_ID_2 @@ -49,14 +51,14 @@ internal class TimelineItemContentPollFactoryTest { @Test fun `Disclosed poll - not ended, no votes`() = runTest { - Truth.assertThat(factory.create(aPollContent())).isEqualTo(aTimelineItemPollContent()) + Truth.assertThat(factory.create(aPollContent(), eventId = null)).isEqualTo(aTimelineItemPollContent()) } @Test fun `Disclosed poll - not ended, some votes, including one from current user`() = runTest { val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id } Truth.assertThat( - factory.create(aPollContent(votes = votes)) + factory.create(aPollContent(votes = votes), eventId = null) ) .isEqualTo( aTimelineItemPollContent( @@ -73,7 +75,7 @@ internal class TimelineItemContentPollFactoryTest { @Test fun `Disclosed poll - ended, no votes, no winner`() = runTest { Truth.assertThat( - factory.create(aPollContent(endTime = 1UL)) + factory.create(aPollContent(endTime = 1UL), eventId = null) ).isEqualTo( aTimelineItemPollContent().let { it.copy( @@ -88,7 +90,7 @@ internal class TimelineItemContentPollFactoryTest { fun `Disclosed poll - ended, some votes, including one from current user (winner)`() = runTest { val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id } Truth.assertThat( - factory.create(aPollContent(votes = votes, endTime = 1UL)) + factory.create(aPollContent(votes = votes, endTime = 1UL), eventId = null) ) .isEqualTo( aTimelineItemPollContent( @@ -107,7 +109,7 @@ internal class TimelineItemContentPollFactoryTest { fun `Disclosed poll - ended, some votes, including one from current user (not winner) and two winning votes`() = runTest { val votes = OTHER_WINNING_VOTES.mapKeys { it.key.id } Truth.assertThat( - factory.create(aPollContent(votes = votes, endTime = 1UL)) + factory.create(aPollContent(votes = votes, endTime = 1UL), eventId = null) ) .isEqualTo( aTimelineItemPollContent( @@ -125,9 +127,9 @@ internal class TimelineItemContentPollFactoryTest { @Test fun `Undisclosed poll - not ended, no votes`() = runTest { Truth.assertThat( - factory.create(aPollContent(PollKind.Undisclosed).copy()) + factory.create(aPollContent(PollKind.Undisclosed).copy(), eventId = null) ).isEqualTo( - aTimelineItemPollContent(PollKind.Undisclosed).let { + aTimelineItemPollContent(pollKind = PollKind.Undisclosed).let { it.copy(answerItems = it.answerItems.map { answerItem -> answerItem.copy(isDisclosed = false) }) } ) @@ -137,7 +139,7 @@ internal class TimelineItemContentPollFactoryTest { fun `Undisclosed poll - not ended, some votes, including one from current user`() = runTest { val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id } Truth.assertThat( - factory.create(aPollContent(pollKind = PollKind.Undisclosed, votes = votes)) + factory.create(aPollContent(pollKind = PollKind.Undisclosed, votes = votes), eventId = null) ) .isEqualTo( aTimelineItemPollContent( @@ -155,7 +157,7 @@ internal class TimelineItemContentPollFactoryTest { @Test fun `Undisclosed poll - ended, no votes, no winner`() = runTest { Truth.assertThat( - factory.create(aPollContent(pollKind = PollKind.Undisclosed, endTime = 1UL)) + factory.create(aPollContent(pollKind = PollKind.Undisclosed, endTime = 1UL), eventId = null) ).isEqualTo( aTimelineItemPollContent().let { it.copy( @@ -173,7 +175,7 @@ internal class TimelineItemContentPollFactoryTest { fun `Undisclosed poll - ended, some votes, including one from current user (winner)`() = runTest { val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id } Truth.assertThat( - factory.create(aPollContent(pollKind = PollKind.Undisclosed, votes = votes, endTime = 1UL)) + factory.create(aPollContent(pollKind = PollKind.Undisclosed, votes = votes, endTime = 1UL), eventId = null) ) .isEqualTo( aTimelineItemPollContent( @@ -193,7 +195,7 @@ internal class TimelineItemContentPollFactoryTest { fun `Undisclosed poll - ended, some votes, including one from current user (not winner) and two winning votes`() = runTest { val votes = OTHER_WINNING_VOTES.mapKeys { it.key.id } Truth.assertThat( - factory.create(aPollContent(PollKind.Undisclosed).copy(votes = votes, endTime = 1UL)) + factory.create(aPollContent(PollKind.Undisclosed).copy(votes = votes, endTime = 1UL), eventId = null) ) .isEqualTo( aTimelineItemPollContent( @@ -209,6 +211,15 @@ internal class TimelineItemContentPollFactoryTest { ) } + @Test + fun `eventId is populated`() = runTest { + Truth.assertThat(factory.create(aPollContent(), eventId = null)) + .isEqualTo(aTimelineItemPollContent(eventId = null)) + + Truth.assertThat(factory.create(aPollContent(), eventId = AN_EVENT_ID)) + .isEqualTo(aTimelineItemPollContent(eventId = AN_EVENT_ID)) + } + private fun aPollContent( pollKind: PollKind = PollKind.Disclosed, votes: Map> = emptyMap(), @@ -223,6 +234,7 @@ internal class TimelineItemContentPollFactoryTest { ) private fun aTimelineItemPollContent( + eventId: EventId? = null, pollKind: PollKind = PollKind.Disclosed, answerItems: List = listOf( aPollAnswerItem(A_POLL_ANSWER_1), @@ -232,6 +244,7 @@ internal class TimelineItemContentPollFactoryTest { ), isEnded: Boolean = false, ) = TimelineItemPollContent( + eventId = eventId, question = A_POLL_QUESTION, answerItems = answerItems, pollKind = pollKind, diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt index 419aa21204..438c14456c 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt @@ -35,6 +35,7 @@ import io.element.android.libraries.designsystem.preview.DayNightPreviews import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.poll.PollAnswer import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.theme.ElementTheme @@ -43,13 +44,18 @@ import kotlinx.collections.immutable.ImmutableList @Composable fun PollContentView( + eventId: EventId?, question: String, answerItems: ImmutableList, pollKind: PollKind, isPollEnded: Boolean, - onAnswerSelected: (PollAnswer) -> Unit, + onAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit, modifier: Modifier = Modifier, ) { + fun onAnswerSelected(pollAnswer: PollAnswer) { + eventId?.let { onAnswerSelected(it, pollAnswer.id) } + } + Column( modifier = modifier .selectableGroup() @@ -58,7 +64,7 @@ fun PollContentView( ) { PollTitle(title = question) - PollAnswers(answerItems = answerItems, onAnswerSelected = onAnswerSelected) + PollAnswers(answerItems = answerItems, onAnswerSelected = ::onAnswerSelected) when { isPollEnded || pollKind == PollKind.Disclosed -> DisclosedPollBottomNotice(answerItems) @@ -134,11 +140,12 @@ fun ColumnScope.UndisclosedPollBottomNotice(modifier: Modifier = Modifier) { @Composable internal fun PollContentUndisclosedPreview() = ElementPreview { PollContentView( + eventId = EventId("\$anEventId"), question = "What type of food should we have at the party?", answerItems = aPollAnswerItemList(isDisclosed = false), pollKind = PollKind.Undisclosed, isPollEnded = false, - onAnswerSelected = { }, + onAnswerSelected = { _, _ -> }, ) } @@ -146,11 +153,12 @@ internal fun PollContentUndisclosedPreview() = ElementPreview { @Composable internal fun PollContentDisclosedPreview() = ElementPreview { PollContentView( + eventId = EventId("\$anEventId"), question = "What type of food should we have at the party?", answerItems = aPollAnswerItemList(), pollKind = PollKind.Disclosed, isPollEnded = false, - onAnswerSelected = { }, + onAnswerSelected = { _, _ -> }, ) } @@ -158,10 +166,11 @@ internal fun PollContentDisclosedPreview() = ElementPreview { @Composable internal fun PollContentEndedPreview() = ElementPreview { PollContentView( + eventId = EventId("\$anEventId"), question = "What type of food should we have at the party?", answerItems = aPollAnswerItemList(isEnded = true), pollKind = PollKind.Disclosed, isPollEnded = false, - onAnswerSelected = { }, + onAnswerSelected = { _, _ -> }, ) }