Skip to content

Commit 2e6581a

Browse files
author
Marco Romano
authored
Show poll creator view in timeline (#1429)
- Shows edit/end poll buttons when the user is the creator of the poll. - Only the end poll button is wired right now as there is no "edit poll" screen yet.
1 parent d8fbf21 commit 2e6581a

File tree

109 files changed

+234
-17
lines changed

Some content is hidden

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

109 files changed

+234
-17
lines changed

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import androidx.compose.runtime.setValue
3030
import dagger.assisted.Assisted
3131
import dagger.assisted.AssistedFactory
3232
import dagger.assisted.AssistedInject
33-
import im.vector.app.features.analytics.plan.PollEnd
3433
import io.element.android.features.messages.impl.actionlist.ActionListEvents
3534
import io.element.android.features.messages.impl.actionlist.ActionListPresenter
3635
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
@@ -39,6 +38,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer
3938
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
4039
import io.element.android.features.messages.impl.timeline.TimelineEvents
4140
import io.element.android.features.messages.impl.timeline.TimelinePresenter
41+
import io.element.android.features.messages.impl.timeline.TimelineState
4242
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter
4343
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryPresenter
4444
import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter
@@ -76,7 +76,6 @@ import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType
7676
import io.element.android.libraries.matrix.ui.room.canRedactAsState
7777
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
7878
import io.element.android.libraries.textcomposer.MessageComposerMode
79-
import io.element.android.services.analytics.api.AnalyticsService
8079
import kotlinx.coroutines.CoroutineScope
8180
import kotlinx.coroutines.launch
8281
import kotlinx.coroutines.withContext
@@ -95,7 +94,6 @@ class MessagesPresenter @AssistedInject constructor(
9594
private val messageSummaryFormatter: MessageSummaryFormatter,
9695
private val dispatchers: CoroutineDispatchers,
9796
private val clipboardHelper: ClipboardHelper,
98-
private val analyticsService: AnalyticsService,
9997
private val preferencesStore: PreferencesStore,
10098
@Assisted private val navigator: MessagesNavigator,
10199
) : Presenter<MessagesState> {
@@ -155,6 +153,7 @@ class MessagesPresenter @AssistedInject constructor(
155153
targetEvent = event.event,
156154
composerState = composerState,
157155
enableTextFormatting = enableTextFormatting,
156+
timelineState = timelineState,
158157
)
159158
}
160159
is MessagesEvents.ToggleReaction -> {
@@ -206,6 +205,7 @@ class MessagesPresenter @AssistedInject constructor(
206205
targetEvent: TimelineItem.Event,
207206
composerState: MessageComposerState,
208207
enableTextFormatting: Boolean,
208+
timelineState: TimelineState,
209209
) = launch {
210210
when (action) {
211211
TimelineItemAction.Copy -> handleCopyContents(targetEvent)
@@ -216,7 +216,7 @@ class MessagesPresenter @AssistedInject constructor(
216216
TimelineItemAction.ViewSource -> handleShowDebugInfoAction(targetEvent)
217217
TimelineItemAction.Forward -> handleForwardAction(targetEvent)
218218
TimelineItemAction.ReportContent -> handleReportAction(targetEvent)
219-
TimelineItemAction.EndPoll -> handleEndPollAction(targetEvent)
219+
TimelineItemAction.EndPoll -> handleEndPollAction(targetEvent, timelineState)
220220
}
221221
}
222222

@@ -266,7 +266,7 @@ class MessagesPresenter @AssistedInject constructor(
266266
targetEvent: TimelineItem.Event,
267267
composerState: MessageComposerState,
268268
enableTextFormatting: Boolean,
269-
) {
269+
) {
270270
val composerMode = MessageComposerMode.Edit(
271271
targetEvent.eventId,
272272
(targetEvent.content as? TimelineItemTextBasedContent)?.let {
@@ -344,11 +344,11 @@ class MessagesPresenter @AssistedInject constructor(
344344
navigator.onReportContentClicked(event.eventId, event.senderId)
345345
}
346346

347-
private suspend fun handleEndPollAction(event: TimelineItem.Event) {
348-
event.eventId?.let {
349-
room.endPoll(it, "The poll with event id: $it has ended.")
350-
analyticsService.capture(PollEnd())
351-
}
347+
private fun handleEndPollAction(
348+
event: TimelineItem.Event,
349+
timelineState: TimelineState,
350+
) {
351+
event.eventId?.let { timelineState.eventSink(TimelineEvents.PollEndClicked(it)) }
352352
}
353353

354354
private suspend fun handleCopyContents(event: TimelineItem.Event) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class ActionListPresenter @Inject constructor(
108108
buildList {
109109
val isMineOrCanRedact = timelineItem.isMine || userCanRedact
110110

111-
// TODO Poll: Reply to poll. Ensure to update `fun TimelineItemEventContent.canBeReplied()`
111+
// TODO Polls: Reply to poll. Ensure to update `fun TimelineItemEventContent.canBeReplied()`
112112
// when touching this
113113
// if (timelineItem.isRemote) {
114114
// // Can only reply or forward messages already uploaded to the server

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,8 @@ sealed interface TimelineEvents {
2626
val pollStartId: EventId,
2727
val answerId: String
2828
) : TimelineEvents
29+
30+
data class PollEndClicked(
31+
val pollStartId: EventId,
32+
) : TimelineEvents
2933
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import androidx.compose.runtime.mutableStateOf
2626
import androidx.compose.runtime.remember
2727
import androidx.compose.runtime.rememberCoroutineScope
2828
import androidx.compose.runtime.saveable.rememberSaveable
29+
import im.vector.app.features.analytics.plan.PollEnd
2930
import im.vector.app.features.analytics.plan.PollVote
3031
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
3132
import io.element.android.features.messages.impl.timeline.model.TimelineItem
@@ -98,11 +99,18 @@ class TimelinePresenter @Inject constructor(
9899
)
99100
analyticsService.capture(PollVote())
100101
}
102+
is TimelineEvents.PollEndClicked -> appScope.launch {
103+
room.endPoll(
104+
pollStartId = event.pollStartId,
105+
text = "The poll with event id: ${event.pollStartId} has ended."
106+
)
107+
analyticsService.capture(PollEnd())
108+
}
101109
}
102110
}
103111

104112
LaunchedEffect(timelineItems.size) {
105-
computeHasNewItems(timelineItems, prevMostRecentItemId, hasNewItems)
113+
computeHasNewItems(timelineItems, prevMostRecentItemId, hasNewItems)
106114
}
107115

108116
LaunchedEffect(Unit) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@ private fun MessageEventBubbleContent(
386386
) {
387387
TimelineItemEventContentView(
388388
content = event.content,
389+
isMine = event.isMine,
389390
interactionSource = interactionSource,
390391
onClick = onMessageClick,
391392
onLongClick = onMessageLongClick,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ fun TimelineItemStateEventRow(
6767
) {
6868
TimelineItemEventContentView(
6969
content = event.content,
70+
isMine = event.isMine,
7071
interactionSource = interactionSource,
7172
onClick = onClick,
7273
onLongClick = onLongClick,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
3636
@Composable
3737
fun TimelineItemEventContentView(
3838
content: TimelineItemEventContent,
39+
isMine: Boolean,
3940
interactionSource: MutableInteractionSource,
4041
extraPadding: ExtraPadding,
4142
onClick: () -> Unit,
@@ -95,6 +96,7 @@ fun TimelineItemEventContentView(
9596
)
9697
is TimelineItemPollContent -> TimelineItemPollView(
9798
content = content,
99+
isMine = isMine,
98100
eventSink = eventSink,
99101
modifier = modifier,
100102
)

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,28 @@ import kotlinx.collections.immutable.toImmutableList
3131
@Composable
3232
fun TimelineItemPollView(
3333
content: TimelineItemPollContent,
34+
isMine: Boolean,
3435
eventSink: (TimelineEvents) -> Unit,
3536
modifier: Modifier = Modifier,
3637
) {
3738
fun onAnswerSelected(pollStartId: EventId, answerId: String) {
3839
eventSink(TimelineEvents.PollAnswerSelected(pollStartId, answerId))
3940
}
4041

42+
fun onPollEnd(pollStartId: EventId) {
43+
eventSink(TimelineEvents.PollEndClicked(pollStartId))
44+
}
45+
4146
PollContentView(
4247
eventId = content.eventId,
4348
question = content.question,
4449
answerItems = content.answerItems.toImmutableList(),
4550
pollKind = content.pollKind,
4651
isPollEnded = content.isEnded,
52+
isMine = isMine,
4753
onAnswerSelected = ::onAnswerSelected,
54+
onPollEdit = {}, // TODO Polls: Wire up this callback once poll edit screen is done.
55+
onPollEnd = ::onPollEnd,
4856
modifier = modifier,
4957
)
5058
}
@@ -55,6 +63,18 @@ internal fun TimelineItemPollViewPreview(@PreviewParameter(TimelineItemPollConte
5563
ElementPreview {
5664
TimelineItemPollView(
5765
content = content,
66+
isMine = false,
67+
eventSink = {},
68+
)
69+
}
70+
71+
@PreviewsDayNight
72+
@Composable
73+
internal fun TimelineItemPollCreatorViewPreview(@PreviewParameter(TimelineItemPollContentProvider::class) content: TimelineItemPollContent) =
74+
ElementPreview {
75+
TimelineItemPollView(
76+
content = content,
77+
isMine = true,
5878
eventSink = {},
5979
)
6080
}

features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,6 @@ class MessagesPresenterTest {
644644
messageSummaryFormatter = FakeMessageSummaryFormatter(),
645645
navigator = navigator,
646646
clipboardHelper = clipboardHelper,
647-
analyticsService = analyticsService,
648647
preferencesStore = preferencesStore,
649648
dispatchers = coroutineDispatchers,
650649
)

features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import app.cash.molecule.RecompositionMode
2020
import app.cash.molecule.moleculeFlow
2121
import app.cash.turbine.test
2222
import com.google.common.truth.Truth.assertThat
23+
import im.vector.app.features.analytics.plan.PollEnd
2324
import im.vector.app.features.analytics.plan.PollVote
25+
import io.element.android.features.messages.fixtures.aMessageEvent
2426
import io.element.android.features.messages.fixtures.aTimelineItemsFactory
2527
import io.element.android.features.messages.impl.timeline.TimelineEvents
2628
import io.element.android.features.messages.impl.timeline.TimelinePresenter
@@ -42,6 +44,7 @@ import io.element.android.services.analytics.test.FakeAnalyticsService
4244
import io.element.android.tests.testutils.WarmUpRule
4345
import io.element.android.tests.testutils.awaitWithLatch
4446
import io.element.android.tests.testutils.testCoroutineDispatchers
47+
import io.element.android.tests.testutils.waitForPredicate
4548
import kotlinx.coroutines.delay
4649
import kotlinx.coroutines.test.TestScope
4750
import kotlinx.coroutines.test.runTest
@@ -280,6 +283,29 @@ class TimelinePresenterTest {
280283
assertThat(analyticsService.capturedEvents.last()).isEqualTo(PollVote())
281284
}
282285

286+
@Test
287+
fun `present - PollEndClicked event calls into rust room api and analytics`() = runTest {
288+
val room = FakeMatrixRoom()
289+
val analyticsService = FakeAnalyticsService()
290+
val presenter = createTimelinePresenter(
291+
room = room,
292+
analyticsService = analyticsService,
293+
)
294+
moleculeFlow(RecompositionMode.Immediate) {
295+
presenter.present()
296+
}.test {
297+
val initialState = awaitItem()
298+
initialState.eventSink(TimelineEvents.PollEndClicked(aMessageEvent().eventId!!))
299+
waitForPredicate { room.endPollInvocations.size == 1 }
300+
cancelAndIgnoreRemainingEvents()
301+
assertThat(room.endPollInvocations.size).isEqualTo(1)
302+
assertThat(room.endPollInvocations.first().pollStartId).isEqualTo(AN_EVENT_ID)
303+
assertThat(room.endPollInvocations.first().text).isEqualTo("The poll with event id: \$anEventId has ended.")
304+
assertThat(analyticsService.capturedEvents.size).isEqualTo(1)
305+
assertThat(analyticsService.capturedEvents.last()).isEqualTo(PollEnd())
306+
}
307+
}
308+
283309
private fun TestScope.createTimelinePresenter(
284310
timeline: MatrixTimeline = FakeMatrixTimeline(),
285311
timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory()

features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@ package io.element.android.features.poll.api
1919
import io.element.android.libraries.matrix.api.poll.PollAnswer
2020
import kotlinx.collections.immutable.persistentListOf
2121

22-
fun aPollAnswerItemList(isEnded: Boolean = false, isDisclosed: Boolean = true) = persistentListOf(
22+
fun aPollAnswerItemList(
23+
hasVotes: Boolean = true,
24+
isEnded: Boolean = false,
25+
isDisclosed: Boolean = true,
26+
) = persistentListOf(
2327
aPollAnswerItem(
2428
answer = PollAnswer("option_1", "Italian \uD83C\uDDEE\uD83C\uDDF9"),
2529
isDisclosed = isDisclosed,
2630
isEnabled = !isEnded,
2731
isWinner = isEnded,
28-
votesCount = 5,
32+
votesCount = if (hasVotes) 5 else 0,
2933
percentage = 0.5f
3034
),
3135
aPollAnswerItem(
@@ -42,7 +46,7 @@ fun aPollAnswerItemList(isEnded: Boolean = false, isDisclosed: Boolean = true) =
4246
isEnabled = !isEnded,
4347
isWinner = false,
4448
isSelected = true,
45-
votesCount = 1,
49+
votesCount = if (hasVotes) 1 else 0,
4650
percentage = 0.1f
4751
),
4852
aPollAnswerItem(isDisclosed = isDisclosed, isEnabled = !isEnded),

0 commit comments

Comments
 (0)