Skip to content

Commit 8a62abe

Browse files
Florian14ElementBot
andauthored
"View only" polls in the timeline (#1031)
* Handle poll events from the sdk * Render started poll event in the timeline * Create poll module * Check poll kind before revealing the results * Check if user has voted before revealing the results * Add active poll previews * Minor cleanup * Update todos * Fix CI * Remove hardcoded string * Update preview * changelog file * Update screenshots * Use CommonPlurals * Set poll root view as selectableGroup * Improve poll result rendering * Update screenshots * Add missing showkase processor * Update screenshots --------- Co-authored-by: ElementBot <[email protected]>
1 parent efdfe15 commit 8a62abe

File tree

50 files changed

+1085
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1085
-1
lines changed

changelog.d/1031.wip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Poll] Render start event in the timeline

features/messages/impl/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ dependencies {
3434
anvil(projects.anvilcodegen)
3535
api(projects.features.messages.api)
3636
implementation(projects.features.location.api)
37+
implementation(projects.features.poll.api)
3738
implementation(projects.libraries.androidutils)
3839
implementation(projects.libraries.core)
3940
implementation(projects.libraries.architecture)

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
4747
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
4848
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
4949
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
50+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
5051
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
5152
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
5253
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
@@ -277,6 +278,7 @@ class MessagesPresenter @AssistedInject constructor(
277278
is TimelineItemLocationContent -> AttachmentThumbnailInfo(
278279
type = AttachmentThumbnailType.Location,
279280
)
281+
is TimelineItemPollContent, // TODO Polls: handle reply to
280282
is TimelineItemTextBasedContent,
281283
is TimelineItemRedactedContent,
282284
is TimelineItemStateContent,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
6262
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
6363
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
6464
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
65+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
6566
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
6667
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
6768
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
@@ -236,6 +237,7 @@ private fun MessageSummary(event: TimelineItem.Event, modifier: Modifier = Modif
236237
val textContent = remember(event.content) { formatter.format(event) }
237238

238239
when (event.content) {
240+
is TimelineItemPollContent, // TODO Polls: handle summary
239241
is TimelineItemTextBasedContent,
240242
is TimelineItemStateContent,
241243
is TimelineItemEncryptedContent,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemContentView.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
2525
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
2626
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
2727
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
28+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
2829
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
2930
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
3031
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
@@ -90,5 +91,10 @@ fun TimelineItemEventContentView(
9091
content = content,
9192
modifier = modifier
9293
)
94+
is TimelineItemPollContent -> TimelineItemPollView(
95+
content = content,
96+
onAnswerSelected = {},
97+
modifier = modifier,
98+
)
9399
}
94100
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.features.messages.impl.timeline.components.event
18+
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.tooling.preview.PreviewParameter
22+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
23+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContentProvider
24+
import io.element.android.features.poll.api.ActivePollContentView
25+
import io.element.android.libraries.designsystem.preview.DayNightPreviews
26+
import io.element.android.libraries.designsystem.preview.ElementPreview
27+
import io.element.android.libraries.matrix.api.poll.PollAnswer
28+
import kotlinx.collections.immutable.toImmutableList
29+
30+
@Composable
31+
fun TimelineItemPollView(
32+
content: TimelineItemPollContent,
33+
onAnswerSelected: (PollAnswer) -> Unit,
34+
modifier: Modifier = Modifier,
35+
) {
36+
ActivePollContentView(
37+
question = content.question,
38+
answerItems = content.answerItems.toImmutableList(),
39+
pollKind = content.pollKind,
40+
onAnswerSelected = onAnswerSelected,
41+
modifier = modifier,
42+
)
43+
}
44+
45+
@DayNightPreviews
46+
@Composable
47+
internal fun TimelineItemPollViewPreview(@PreviewParameter(TimelineItemPollContentProvider::class) content: TimelineItemPollContent) =
48+
ElementPreview {
49+
TimelineItemPollView(
50+
content = content,
51+
onAnswerSelected = {},
52+
)
53+
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventTimeline
2222
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
2323
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
2424
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
25+
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
26+
import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent
2527
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
2628
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
2729
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
@@ -35,6 +37,8 @@ class TimelineItemContentFactory @Inject constructor(
3537
private val messageFactory: TimelineItemContentMessageFactory,
3638
private val redactedMessageFactory: TimelineItemContentRedactedFactory,
3739
private val stickerFactory: TimelineItemContentStickerFactory,
40+
private val pollFactory: TimelineItemContentPollFactory,
41+
private val pollEndFactory: TimelineItemContentPollEndFactory,
3842
private val utdFactory: TimelineItemContentUTDFactory,
3943
private val roomMembershipFactory: TimelineItemContentRoomMembershipFactory,
4044
private val profileChangeFactory: TimelineItemContentProfileChangeFactory,
@@ -53,6 +57,8 @@ class TimelineItemContentFactory @Inject constructor(
5357
is RoomMembershipContent -> roomMembershipFactory.create(eventTimelineItem)
5458
is StateContent -> stateFactory.create(eventTimelineItem)
5559
is StickerContent -> stickerFactory.create(itemContent)
60+
is PollContent -> pollFactory.create(itemContent)
61+
is PollEndContent -> pollEndFactory.create(itemContent)
5662
is UnableToDecryptContent -> utdFactory.create(itemContent)
5763
is UnknownContent -> TimelineItemUnknownContent
5864
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.features.messages.impl.timeline.factories.event
18+
19+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
20+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
21+
import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent
22+
import javax.inject.Inject
23+
24+
class TimelineItemContentPollEndFactory @Inject constructor() {
25+
26+
fun create(@Suppress("UNUSED_PARAMETER") content: PollEndContent): TimelineItemEventContent {
27+
return TimelineItemUnknownContent
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.features.messages.impl.timeline.factories.event
18+
19+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
20+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
21+
import io.element.android.features.poll.api.PollAnswerItem
22+
import io.element.android.libraries.matrix.api.MatrixClient
23+
import io.element.android.libraries.matrix.api.poll.PollKind
24+
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
25+
import javax.inject.Inject
26+
27+
class TimelineItemContentPollFactory @Inject constructor(
28+
private val matrixClient: MatrixClient,
29+
) {
30+
31+
fun create(content: PollContent): TimelineItemEventContent {
32+
// Todo Move this computation to the matrix rust sdk
33+
val showResults = content.kind == PollKind.Disclosed && matrixClient.sessionId in content.votes.flatMap { it.value }
34+
val pollVotesCount = content.votes.flatMap { it.value }.size
35+
val userVotes = content.votes.filter { matrixClient.sessionId in it.value }.keys
36+
val answerItems = content.answers.map { answer ->
37+
val votesCount = content.votes[answer.id]?.size ?: 0
38+
val progress = if (pollVotesCount > 0) votesCount.toFloat() / pollVotesCount.toFloat() else 0f
39+
PollAnswerItem(
40+
answer = answer,
41+
isSelected = answer.id in userVotes,
42+
isDisclosed = showResults,
43+
votesCount = votesCount,
44+
progress = progress,
45+
)
46+
}
47+
48+
return TimelineItemPollContent(
49+
question = content.question,
50+
answerItems = answerItems,
51+
votes = content.votes,
52+
pollKind = content.kind,
53+
isDisclosed = showResults
54+
)
55+
}
56+
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
2222
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
2323
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
2424
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
25+
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
2526
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemProfileChangeContent
2627
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
2728
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRoomMembershipContent
@@ -33,6 +34,8 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
3334
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent
3435
import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent
3536
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
37+
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
38+
import io.element.android.libraries.matrix.api.timeline.item.event.PollEndContent
3639
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
3740
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
3841
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
@@ -55,6 +58,7 @@ internal fun TimelineItem.Event.canBeGrouped(): Boolean {
5558
is TimelineItemVideoContent,
5659
is TimelineItemAudioContent,
5760
is TimelineItemLocationContent,
61+
is TimelineItemPollContent,
5862
TimelineItemRedactedContent,
5963
TimelineItemUnknownContent -> false
6064
is TimelineItemProfileChangeContent,
@@ -74,6 +78,8 @@ internal fun MatrixTimelineItem.Event.canBeDisplayedInBubbleBlock(): Boolean {
7478
is MessageContent,
7579
RedactedContent,
7680
is StickerContent,
81+
is PollContent,
82+
is PollEndContent,
7783
is UnableToDecryptContent -> true
7884
is FailedToParseStateContent,
7985
is ProfileChangeContent,

0 commit comments

Comments
 (0)