diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index fdf8d994a6..f8467b458e 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
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 = { _, _ -> },
)
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c8af411131..0d52d1e0df 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -146,7 +146,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" }
timber = "com.jakewharton.timber:timber:5.0.1"
-matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.47"
+matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.48"
sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" }
sqldelight-driver-jvm = { module = "com.squareup.sqldelight:sqlite-driver", version.ref = "sqldelight" }
sqldelight-coroutines = { module = "com.squareup.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
index 698d996e08..60c8b1ddd5 100644
--- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
+++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
@@ -161,6 +161,22 @@ interface MatrixRoom : Closeable {
pollKind: PollKind,
): Result
+ /**
+ * Send a response to a poll.
+ *
+ * @param pollStartId The event ID of the poll start event.
+ * @param answers The list of answer ids to send.
+ */
+ suspend fun sendPollResponse(pollStartId: EventId, answers: List): Result
+
+ /**
+ * Ends a poll in the room.
+ *
+ * @param pollStartId The event ID of the poll start event.
+ * @param text Fallback text of the poll end event.
+ */
+ suspend fun endPoll(pollStartId: EventId, text: String): Result
+
override fun close() = destroy()
}
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfiguration.kt
similarity index 91%
rename from libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt
rename to libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfiguration.kt
index 401fa0ce83..f49ee65208 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfiguration.kt
@@ -23,7 +23,8 @@ val oidcConfiguration: OidcConfiguration = OidcConfiguration(
clientName = "Element",
redirectUri = OidcConfig.redirectUri,
clientUri = "https://element.io",
- tosUri = "https://element.io/user-terms-of-service",
+ logoUri = "https://element.io/mobile-icon.png",
+ tosUri = "https://element.io/acceptable-use-policy-terms",
policyUri = "https://element.io/privacy",
/**
* Some homeservers/auth issuers don't support dynamic client registration, and have to be registered manually
diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
index 0305eed0cc..461e43931e 100644
--- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
+++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
@@ -239,7 +239,7 @@ class RustMatrixRoom(
override suspend fun editMessage(originalEventId: EventId?, transactionId: TransactionId?, message: String): Result = withContext(roomDispatcher) {
if (originalEventId != null) {
runCatching {
- innerRoom.edit(/* TODO use content */ message, originalEventId.value, transactionId?.value)
+ innerRoom.edit(messageEventContentFromMarkdown(message), originalEventId.value, transactionId?.value)
}
} else {
runCatching {
@@ -250,10 +250,8 @@ class RustMatrixRoom(
}
override suspend fun replyMessage(eventId: EventId, message: String): Result = withContext(roomDispatcher) {
- val transactionId = genTransactionId()
- // val content = messageEventContentFromMarkdown(message)
runCatching {
- innerRoom.sendReply(/* TODO use content */ message, eventId.value, transactionId)
+ innerRoom.sendReply(messageEventContentFromMarkdown(message), eventId.value, genTransactionId())
}
}
@@ -426,6 +424,32 @@ class RustMatrixRoom(
}
}
+ override suspend fun sendPollResponse(
+ pollStartId: EventId,
+ answers: List
+ ): Result = withContext(roomDispatcher) {
+ runCatching {
+ innerRoom.sendPollResponse(
+ pollStartId = pollStartId.value,
+ answers = answers,
+ txnId = genTransactionId(),
+ )
+ }
+ }
+
+ override suspend fun endPoll(
+ pollStartId: EventId,
+ text: String
+ ): Result = withContext(roomDispatcher) {
+ runCatching {
+ innerRoom.endPoll(
+ pollStartId = pollStartId.value,
+ text = text,
+ txnId = genTransactionId(),
+ )
+ }
+ }
+
private suspend fun sendAttachment(files: List, handle: () -> SendAttachmentJoinHandle): Result {
return runCatching {
MediaUploadHandlerImpl(files, handle())
diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
index 7f4440f9a3..c7a17e0134 100644
--- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
+++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
@@ -87,6 +87,8 @@ class FakeMatrixRoom(
private var reportContentResult = Result.success(Unit)
private var sendLocationResult = Result.success(Unit)
private var createPollResult = Result.success(Unit)
+ private var sendPollResponseResult = Result.success(Unit)
+ private var endPollResult = Result.success(Unit)
private var progressCallbackValues = emptyList>()
val editMessageCalls = mutableListOf()
@@ -111,6 +113,12 @@ class FakeMatrixRoom(
private val _createPollInvocations = mutableListOf()
val createPollInvocations: List = _createPollInvocations
+ private val _sendPollResponseInvocations = mutableListOf()
+ val sendPollResponseInvocations: List = _sendPollResponseInvocations
+
+ private val _endPollInvocations = mutableListOf()
+ val endPollInvocations: List = _endPollInvocations
+
var invitedUserId: UserId? = null
private set
@@ -329,6 +337,22 @@ class FakeMatrixRoom(
return createPollResult
}
+ override suspend fun sendPollResponse(
+ pollStartId: EventId,
+ answers: List
+ ): Result = simulateLongTask {
+ _sendPollResponseInvocations.add(SendPollResponseInvocation(pollStartId, answers))
+ return sendPollResponseResult
+ }
+
+ override suspend fun endPoll(
+ pollStartId: EventId,
+ text: String
+ ): Result = simulateLongTask {
+ _endPollInvocations.add(EndPollInvocation(pollStartId, text))
+ return endPollResult
+ }
+
fun givenLeaveRoomError(throwable: Throwable?) {
this.leaveRoomError = throwable
}
@@ -425,6 +449,14 @@ class FakeMatrixRoom(
createPollResult = result
}
+ fun givenSendPollResponseResult(result: Result) {
+ sendPollResponseResult = result
+ }
+
+ fun givenEndPollResult(result: Result) {
+ endPollResult = result
+ }
+
fun givenProgressCallbackValues(values: List>) {
progressCallbackValues = values
}
@@ -444,3 +476,13 @@ data class CreatePollInvocation(
val maxSelections: Int,
val pollKind: PollKind,
)
+
+data class SendPollResponseInvocation(
+ val pollStartId: EventId,
+ val answers: List,
+)
+
+data class EndPollInvocation(
+ val pollStartId: EventId,
+ val text: String,
+)