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.
This commit is contained in:
parent
54fb1e79c5
commit
295e10e612
109 changed files with 234 additions and 17 deletions
|
|
@ -30,7 +30,6 @@ import androidx.compose.runtime.setValue
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.features.analytics.plan.PollEnd
|
|
||||||
import io.element.android.features.messages.impl.actionlist.ActionListEvents
|
import io.element.android.features.messages.impl.actionlist.ActionListEvents
|
||||||
import io.element.android.features.messages.impl.actionlist.ActionListPresenter
|
import io.element.android.features.messages.impl.actionlist.ActionListPresenter
|
||||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||||
|
|
@ -39,6 +38,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
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.TimelinePresenter
|
||||||
|
import io.element.android.features.messages.impl.timeline.TimelineState
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter
|
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter
|
||||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryPresenter
|
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryPresenter
|
||||||
import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter
|
import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuPresenter
|
||||||
|
|
@ -76,7 +76,6 @@ import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType
|
||||||
import io.element.android.libraries.matrix.ui.room.canRedactAsState
|
import io.element.android.libraries.matrix.ui.room.canRedactAsState
|
||||||
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
|
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
|
||||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -95,7 +94,6 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
private val messageSummaryFormatter: MessageSummaryFormatter,
|
private val messageSummaryFormatter: MessageSummaryFormatter,
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
private val clipboardHelper: ClipboardHelper,
|
private val clipboardHelper: ClipboardHelper,
|
||||||
private val analyticsService: AnalyticsService,
|
|
||||||
private val preferencesStore: PreferencesStore,
|
private val preferencesStore: PreferencesStore,
|
||||||
@Assisted private val navigator: MessagesNavigator,
|
@Assisted private val navigator: MessagesNavigator,
|
||||||
) : Presenter<MessagesState> {
|
) : Presenter<MessagesState> {
|
||||||
|
|
@ -155,6 +153,7 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
targetEvent = event.event,
|
targetEvent = event.event,
|
||||||
composerState = composerState,
|
composerState = composerState,
|
||||||
enableTextFormatting = enableTextFormatting,
|
enableTextFormatting = enableTextFormatting,
|
||||||
|
timelineState = timelineState,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is MessagesEvents.ToggleReaction -> {
|
is MessagesEvents.ToggleReaction -> {
|
||||||
|
|
@ -206,6 +205,7 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
targetEvent: TimelineItem.Event,
|
targetEvent: TimelineItem.Event,
|
||||||
composerState: MessageComposerState,
|
composerState: MessageComposerState,
|
||||||
enableTextFormatting: Boolean,
|
enableTextFormatting: Boolean,
|
||||||
|
timelineState: TimelineState,
|
||||||
) = launch {
|
) = launch {
|
||||||
when (action) {
|
when (action) {
|
||||||
TimelineItemAction.Copy -> handleCopyContents(targetEvent)
|
TimelineItemAction.Copy -> handleCopyContents(targetEvent)
|
||||||
|
|
@ -216,7 +216,7 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
TimelineItemAction.ViewSource -> handleShowDebugInfoAction(targetEvent)
|
TimelineItemAction.ViewSource -> handleShowDebugInfoAction(targetEvent)
|
||||||
TimelineItemAction.Forward -> handleForwardAction(targetEvent)
|
TimelineItemAction.Forward -> handleForwardAction(targetEvent)
|
||||||
TimelineItemAction.ReportContent -> handleReportAction(targetEvent)
|
TimelineItemAction.ReportContent -> handleReportAction(targetEvent)
|
||||||
TimelineItemAction.EndPoll -> handleEndPollAction(targetEvent)
|
TimelineItemAction.EndPoll -> handleEndPollAction(targetEvent, timelineState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,7 +266,7 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
targetEvent: TimelineItem.Event,
|
targetEvent: TimelineItem.Event,
|
||||||
composerState: MessageComposerState,
|
composerState: MessageComposerState,
|
||||||
enableTextFormatting: Boolean,
|
enableTextFormatting: Boolean,
|
||||||
) {
|
) {
|
||||||
val composerMode = MessageComposerMode.Edit(
|
val composerMode = MessageComposerMode.Edit(
|
||||||
targetEvent.eventId,
|
targetEvent.eventId,
|
||||||
(targetEvent.content as? TimelineItemTextBasedContent)?.let {
|
(targetEvent.content as? TimelineItemTextBasedContent)?.let {
|
||||||
|
|
@ -344,11 +344,11 @@ class MessagesPresenter @AssistedInject constructor(
|
||||||
navigator.onReportContentClicked(event.eventId, event.senderId)
|
navigator.onReportContentClicked(event.eventId, event.senderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleEndPollAction(event: TimelineItem.Event) {
|
private fun handleEndPollAction(
|
||||||
event.eventId?.let {
|
event: TimelineItem.Event,
|
||||||
room.endPoll(it, "The poll with event id: $it has ended.")
|
timelineState: TimelineState,
|
||||||
analyticsService.capture(PollEnd())
|
) {
|
||||||
}
|
event.eventId?.let { timelineState.eventSink(TimelineEvents.PollEndClicked(it)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleCopyContents(event: TimelineItem.Event) {
|
private suspend fun handleCopyContents(event: TimelineItem.Event) {
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ class ActionListPresenter @Inject constructor(
|
||||||
buildList {
|
buildList {
|
||||||
val isMineOrCanRedact = timelineItem.isMine || userCanRedact
|
val isMineOrCanRedact = timelineItem.isMine || userCanRedact
|
||||||
|
|
||||||
// TODO Poll: Reply to poll. Ensure to update `fun TimelineItemEventContent.canBeReplied()`
|
// TODO Polls: Reply to poll. Ensure to update `fun TimelineItemEventContent.canBeReplied()`
|
||||||
// when touching this
|
// when touching this
|
||||||
// if (timelineItem.isRemote) {
|
// if (timelineItem.isRemote) {
|
||||||
// // Can only reply or forward messages already uploaded to the server
|
// // Can only reply or forward messages already uploaded to the server
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,8 @@ sealed interface TimelineEvents {
|
||||||
val pollStartId: EventId,
|
val pollStartId: EventId,
|
||||||
val answerId: String
|
val answerId: String
|
||||||
) : TimelineEvents
|
) : TimelineEvents
|
||||||
|
|
||||||
|
data class PollEndClicked(
|
||||||
|
val pollStartId: EventId,
|
||||||
|
) : TimelineEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import im.vector.app.features.analytics.plan.PollEnd
|
||||||
import im.vector.app.features.analytics.plan.PollVote
|
import im.vector.app.features.analytics.plan.PollVote
|
||||||
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
|
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
|
|
@ -98,11 +99,18 @@ class TimelinePresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
analyticsService.capture(PollVote())
|
analyticsService.capture(PollVote())
|
||||||
}
|
}
|
||||||
|
is TimelineEvents.PollEndClicked -> appScope.launch {
|
||||||
|
room.endPoll(
|
||||||
|
pollStartId = event.pollStartId,
|
||||||
|
text = "The poll with event id: ${event.pollStartId} has ended."
|
||||||
|
)
|
||||||
|
analyticsService.capture(PollEnd())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(timelineItems.size) {
|
LaunchedEffect(timelineItems.size) {
|
||||||
computeHasNewItems(timelineItems, prevMostRecentItemId, hasNewItems)
|
computeHasNewItems(timelineItems, prevMostRecentItemId, hasNewItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|
|
||||||
|
|
@ -386,6 +386,7 @@ private fun MessageEventBubbleContent(
|
||||||
) {
|
) {
|
||||||
TimelineItemEventContentView(
|
TimelineItemEventContentView(
|
||||||
content = event.content,
|
content = event.content,
|
||||||
|
isMine = event.isMine,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
onClick = onMessageClick,
|
onClick = onMessageClick,
|
||||||
onLongClick = onMessageLongClick,
|
onLongClick = onMessageLongClick,
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ fun TimelineItemStateEventRow(
|
||||||
) {
|
) {
|
||||||
TimelineItemEventContentView(
|
TimelineItemEventContentView(
|
||||||
content = event.content,
|
content = event.content,
|
||||||
|
isMine = event.isMine,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineItemEventContentView(
|
fun TimelineItemEventContentView(
|
||||||
content: TimelineItemEventContent,
|
content: TimelineItemEventContent,
|
||||||
|
isMine: Boolean,
|
||||||
interactionSource: MutableInteractionSource,
|
interactionSource: MutableInteractionSource,
|
||||||
extraPadding: ExtraPadding,
|
extraPadding: ExtraPadding,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
|
|
@ -95,6 +96,7 @@ fun TimelineItemEventContentView(
|
||||||
)
|
)
|
||||||
is TimelineItemPollContent -> TimelineItemPollView(
|
is TimelineItemPollContent -> TimelineItemPollView(
|
||||||
content = content,
|
content = content,
|
||||||
|
isMine = isMine,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import kotlinx.collections.immutable.toImmutableList
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineItemPollView(
|
fun TimelineItemPollView(
|
||||||
content: TimelineItemPollContent,
|
content: TimelineItemPollContent,
|
||||||
|
isMine: Boolean,
|
||||||
eventSink: (TimelineEvents) -> Unit,
|
eventSink: (TimelineEvents) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -38,13 +39,20 @@ fun TimelineItemPollView(
|
||||||
eventSink(TimelineEvents.PollAnswerSelected(pollStartId, answerId))
|
eventSink(TimelineEvents.PollAnswerSelected(pollStartId, answerId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onPollEnd(pollStartId: EventId) {
|
||||||
|
eventSink(TimelineEvents.PollEndClicked(pollStartId))
|
||||||
|
}
|
||||||
|
|
||||||
PollContentView(
|
PollContentView(
|
||||||
eventId = content.eventId,
|
eventId = content.eventId,
|
||||||
question = content.question,
|
question = content.question,
|
||||||
answerItems = content.answerItems.toImmutableList(),
|
answerItems = content.answerItems.toImmutableList(),
|
||||||
pollKind = content.pollKind,
|
pollKind = content.pollKind,
|
||||||
isPollEnded = content.isEnded,
|
isPollEnded = content.isEnded,
|
||||||
|
isMine = isMine,
|
||||||
onAnswerSelected = ::onAnswerSelected,
|
onAnswerSelected = ::onAnswerSelected,
|
||||||
|
onPollEdit = {}, // TODO Polls: Wire up this callback once poll edit screen is done.
|
||||||
|
onPollEnd = ::onPollEnd,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -55,6 +63,18 @@ internal fun TimelineItemPollViewPreview(@PreviewParameter(TimelineItemPollConte
|
||||||
ElementPreview {
|
ElementPreview {
|
||||||
TimelineItemPollView(
|
TimelineItemPollView(
|
||||||
content = content,
|
content = content,
|
||||||
|
isMine = false,
|
||||||
|
eventSink = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun TimelineItemPollCreatorViewPreview(@PreviewParameter(TimelineItemPollContentProvider::class) content: TimelineItemPollContent) =
|
||||||
|
ElementPreview {
|
||||||
|
TimelineItemPollView(
|
||||||
|
content = content,
|
||||||
|
isMine = true,
|
||||||
eventSink = {},
|
eventSink = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -644,7 +644,6 @@ class MessagesPresenterTest {
|
||||||
messageSummaryFormatter = FakeMessageSummaryFormatter(),
|
messageSummaryFormatter = FakeMessageSummaryFormatter(),
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
clipboardHelper = clipboardHelper,
|
clipboardHelper = clipboardHelper,
|
||||||
analyticsService = analyticsService,
|
|
||||||
preferencesStore = preferencesStore,
|
preferencesStore = preferencesStore,
|
||||||
dispatchers = coroutineDispatchers,
|
dispatchers = coroutineDispatchers,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import im.vector.app.features.analytics.plan.PollEnd
|
||||||
import im.vector.app.features.analytics.plan.PollVote
|
import im.vector.app.features.analytics.plan.PollVote
|
||||||
|
import io.element.android.features.messages.fixtures.aMessageEvent
|
||||||
import io.element.android.features.messages.fixtures.aTimelineItemsFactory
|
import io.element.android.features.messages.fixtures.aTimelineItemsFactory
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
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.TimelinePresenter
|
||||||
|
|
@ -42,6 +44,7 @@ import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.awaitWithLatch
|
import io.element.android.tests.testutils.awaitWithLatch
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
|
import io.element.android.tests.testutils.waitForPredicate
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -280,6 +283,29 @@ class TimelinePresenterTest {
|
||||||
assertThat(analyticsService.capturedEvents.last()).isEqualTo(PollVote())
|
assertThat(analyticsService.capturedEvents.last()).isEqualTo(PollVote())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - PollEndClicked event calls into rust room api and analytics`() = runTest {
|
||||||
|
val room = FakeMatrixRoom()
|
||||||
|
val analyticsService = FakeAnalyticsService()
|
||||||
|
val presenter = createTimelinePresenter(
|
||||||
|
room = room,
|
||||||
|
analyticsService = analyticsService,
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink(TimelineEvents.PollEndClicked(aMessageEvent().eventId!!))
|
||||||
|
waitForPredicate { room.endPollInvocations.size == 1 }
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
assertThat(room.endPollInvocations.size).isEqualTo(1)
|
||||||
|
assertThat(room.endPollInvocations.first().pollStartId).isEqualTo(AN_EVENT_ID)
|
||||||
|
assertThat(room.endPollInvocations.first().text).isEqualTo("The poll with event id: \$anEventId has ended.")
|
||||||
|
assertThat(analyticsService.capturedEvents.size).isEqualTo(1)
|
||||||
|
assertThat(analyticsService.capturedEvents.last()).isEqualTo(PollEnd())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun TestScope.createTimelinePresenter(
|
private fun TestScope.createTimelinePresenter(
|
||||||
timeline: MatrixTimeline = FakeMatrixTimeline(),
|
timeline: MatrixTimeline = FakeMatrixTimeline(),
|
||||||
timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory()
|
timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory()
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,17 @@ package io.element.android.features.poll.api
|
||||||
import io.element.android.libraries.matrix.api.poll.PollAnswer
|
import io.element.android.libraries.matrix.api.poll.PollAnswer
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
fun aPollAnswerItemList(isEnded: Boolean = false, isDisclosed: Boolean = true) = persistentListOf(
|
fun aPollAnswerItemList(
|
||||||
|
hasVotes: Boolean = true,
|
||||||
|
isEnded: Boolean = false,
|
||||||
|
isDisclosed: Boolean = true,
|
||||||
|
) = persistentListOf(
|
||||||
aPollAnswerItem(
|
aPollAnswerItem(
|
||||||
answer = PollAnswer("option_1", "Italian \uD83C\uDDEE\uD83C\uDDF9"),
|
answer = PollAnswer("option_1", "Italian \uD83C\uDDEE\uD83C\uDDF9"),
|
||||||
isDisclosed = isDisclosed,
|
isDisclosed = isDisclosed,
|
||||||
isEnabled = !isEnded,
|
isEnabled = !isEnded,
|
||||||
isWinner = isEnded,
|
isWinner = isEnded,
|
||||||
votesCount = 5,
|
votesCount = if (hasVotes) 5 else 0,
|
||||||
percentage = 0.5f
|
percentage = 0.5f
|
||||||
),
|
),
|
||||||
aPollAnswerItem(
|
aPollAnswerItem(
|
||||||
|
|
@ -42,7 +46,7 @@ fun aPollAnswerItemList(isEnded: Boolean = false, isDisclosed: Boolean = true) =
|
||||||
isEnabled = !isEnded,
|
isEnabled = !isEnded,
|
||||||
isWinner = false,
|
isWinner = false,
|
||||||
isSelected = true,
|
isSelected = true,
|
||||||
votesCount = 1,
|
votesCount = if (hasVotes) 1 else 0,
|
||||||
percentage = 0.1f
|
percentage = 0.1f
|
||||||
),
|
),
|
||||||
aPollAnswerItem(isDisclosed = isDisclosed, isEnabled = !isEnded),
|
aPollAnswerItem(isDisclosed = isDisclosed, isEnabled = !isEnded),
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,19 @@ import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.selection.selectable
|
import androidx.compose.foundation.selection.selectable
|
||||||
import androidx.compose.foundation.selection.selectableGroup
|
import androidx.compose.foundation.selection.selectableGroup
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
|
|
@ -51,13 +56,37 @@ fun PollContentView(
|
||||||
answerItems: ImmutableList<PollAnswerItem>,
|
answerItems: ImmutableList<PollAnswerItem>,
|
||||||
pollKind: PollKind,
|
pollKind: PollKind,
|
||||||
isPollEnded: Boolean,
|
isPollEnded: Boolean,
|
||||||
|
isMine: Boolean,
|
||||||
onAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit,
|
onAnswerSelected: (pollStartId: EventId, answerId: String) -> Unit,
|
||||||
|
onPollEdit: (pollStartId: EventId) -> Unit,
|
||||||
|
onPollEnd: (pollStartId: EventId) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
val votesCount = remember(answerItems) { answerItems.sumOf { it.votesCount } }
|
||||||
|
|
||||||
fun onAnswerSelected(pollAnswer: PollAnswer) {
|
fun onAnswerSelected(pollAnswer: PollAnswer) {
|
||||||
eventId?.let { onAnswerSelected(it, pollAnswer.id) }
|
eventId?.let { onAnswerSelected(it, pollAnswer.id) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onPollEdit() {
|
||||||
|
eventId?.let { onPollEdit(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPollEnd() {
|
||||||
|
eventId?.let { onPollEnd(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
var showConfirmation: Boolean by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (showConfirmation) ConfirmationDialog(
|
||||||
|
content = stringResource(id = CommonStrings.common_poll_end_confirmation),
|
||||||
|
onSubmitClicked = {
|
||||||
|
onPollEnd()
|
||||||
|
showConfirmation = false
|
||||||
|
},
|
||||||
|
onDismiss = { showConfirmation = false },
|
||||||
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
|
@ -67,11 +96,20 @@ fun PollContentView(
|
||||||
PollAnswers(answerItems = answerItems, onAnswerSelected = ::onAnswerSelected)
|
PollAnswers(answerItems = answerItems, onAnswerSelected = ::onAnswerSelected)
|
||||||
|
|
||||||
if (isPollEnded || pollKind == PollKind.Disclosed) {
|
if (isPollEnded || pollKind == PollKind.Disclosed) {
|
||||||
val votesCount = remember(answerItems) { answerItems.sumOf { it.votesCount } }
|
|
||||||
DisclosedPollBottomNotice(votesCount = votesCount)
|
DisclosedPollBottomNotice(votesCount = votesCount)
|
||||||
} else {
|
} else {
|
||||||
UndisclosedPollBottomNotice()
|
UndisclosedPollBottomNotice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isMine) {
|
||||||
|
CreatorView(
|
||||||
|
votesCount = 1, // TODO Polls: set to `votesCount` when edit poll screen is implemented.
|
||||||
|
isPollEnded = isPollEnded,
|
||||||
|
onPollEdit = ::onPollEdit,
|
||||||
|
onPollEnd = { showConfirmation = true },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,6 +195,31 @@ private fun ColumnScope.UndisclosedPollBottomNotice(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CreatorView(
|
||||||
|
@Suppress("SameParameterValue") votesCount: Int, // TODO Polls: remove @Suppress when edit poll screen is implemented.
|
||||||
|
isPollEnded: Boolean,
|
||||||
|
onPollEdit: () -> Unit,
|
||||||
|
onPollEnd: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
if (!isPollEnded) {
|
||||||
|
if (votesCount == 0) {
|
||||||
|
Button(
|
||||||
|
text = stringResource(id = CommonStrings.action_edit_poll),
|
||||||
|
onClick = onPollEdit,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Button(
|
||||||
|
text = stringResource(id = CommonStrings.action_end_poll),
|
||||||
|
onClick = onPollEnd,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun PollContentUndisclosedPreview() = ElementPreview {
|
internal fun PollContentUndisclosedPreview() = ElementPreview {
|
||||||
|
|
@ -166,7 +229,10 @@ internal fun PollContentUndisclosedPreview() = ElementPreview {
|
||||||
answerItems = aPollAnswerItemList(isDisclosed = false),
|
answerItems = aPollAnswerItemList(isDisclosed = false),
|
||||||
pollKind = PollKind.Undisclosed,
|
pollKind = PollKind.Undisclosed,
|
||||||
isPollEnded = false,
|
isPollEnded = false,
|
||||||
|
isMine = false,
|
||||||
onAnswerSelected = { _, _ -> },
|
onAnswerSelected = { _, _ -> },
|
||||||
|
onPollEdit = {},
|
||||||
|
onPollEnd = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +245,10 @@ internal fun PollContentDisclosedPreview() = ElementPreview {
|
||||||
answerItems = aPollAnswerItemList(),
|
answerItems = aPollAnswerItemList(),
|
||||||
pollKind = PollKind.Disclosed,
|
pollKind = PollKind.Disclosed,
|
||||||
isPollEnded = false,
|
isPollEnded = false,
|
||||||
|
isMine = false,
|
||||||
onAnswerSelected = { _, _ -> },
|
onAnswerSelected = { _, _ -> },
|
||||||
|
onPollEdit = {},
|
||||||
|
onPollEnd = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,6 +261,57 @@ internal fun PollContentEndedPreview() = ElementPreview {
|
||||||
answerItems = aPollAnswerItemList(isEnded = true),
|
answerItems = aPollAnswerItemList(isEnded = true),
|
||||||
pollKind = PollKind.Disclosed,
|
pollKind = PollKind.Disclosed,
|
||||||
isPollEnded = true,
|
isPollEnded = true,
|
||||||
|
isMine = false,
|
||||||
onAnswerSelected = { _, _ -> },
|
onAnswerSelected = { _, _ -> },
|
||||||
|
onPollEdit = {},
|
||||||
|
onPollEnd = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun PollContentCreatorNoVotesPreview() = ElementPreview {
|
||||||
|
PollContentView(
|
||||||
|
eventId = EventId("\$anEventId"),
|
||||||
|
question = "What type of food should we have at the party?",
|
||||||
|
answerItems = aPollAnswerItemList(hasVotes = false, isEnded = false),
|
||||||
|
pollKind = PollKind.Disclosed,
|
||||||
|
isPollEnded = false,
|
||||||
|
isMine = true,
|
||||||
|
onAnswerSelected = { _, _ -> },
|
||||||
|
onPollEdit = {},
|
||||||
|
onPollEnd = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun PollContentCreatorPreview() = ElementPreview {
|
||||||
|
PollContentView(
|
||||||
|
eventId = EventId("\$anEventId"),
|
||||||
|
question = "What type of food should we have at the party?",
|
||||||
|
answerItems = aPollAnswerItemList(isEnded = false),
|
||||||
|
pollKind = PollKind.Disclosed,
|
||||||
|
isPollEnded = false,
|
||||||
|
isMine = true,
|
||||||
|
onAnswerSelected = { _, _ -> },
|
||||||
|
onPollEdit = {},
|
||||||
|
onPollEnd = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun PollContentCreatorEndedPreview() = ElementPreview {
|
||||||
|
PollContentView(
|
||||||
|
eventId = EventId("\$anEventId"),
|
||||||
|
question = "What type of food should we have at the party?",
|
||||||
|
answerItems = aPollAnswerItemList(isEnded = true),
|
||||||
|
pollKind = PollKind.Disclosed,
|
||||||
|
isPollEnded = true,
|
||||||
|
isMine = true,
|
||||||
|
onAnswerSelected = { _, _ -> },
|
||||||
|
onPollEdit = {},
|
||||||
|
onPollEnd = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@
|
||||||
<string name="action_take_photo">"Take photo"</string>
|
<string name="action_take_photo">"Take photo"</string>
|
||||||
<string name="action_view_source">"View Source"</string>
|
<string name="action_view_source">"View Source"</string>
|
||||||
<string name="action_yes">"Yes"</string>
|
<string name="action_yes">"Yes"</string>
|
||||||
|
<string name="action_edit_poll">"Edit poll"</string>
|
||||||
<string name="common_about">"About"</string>
|
<string name="common_about">"About"</string>
|
||||||
<string name="common_acceptable_use_policy">"Acceptable use policy"</string>
|
<string name="common_acceptable_use_policy">"Acceptable use policy"</string>
|
||||||
<string name="common_advanced_settings">"Advanced settings"</string>
|
<string name="common_advanced_settings">"Advanced settings"</string>
|
||||||
|
|
@ -149,6 +150,7 @@
|
||||||
<string name="common_verification_complete">"Verification complete"</string>
|
<string name="common_verification_complete">"Verification complete"</string>
|
||||||
<string name="common_video">"Video"</string>
|
<string name="common_video">"Video"</string>
|
||||||
<string name="common_waiting">"Waiting…"</string>
|
<string name="common_waiting">"Waiting…"</string>
|
||||||
|
<string name="common_poll_end_confirmation">"Are you sure you want to end this poll?"</string>
|
||||||
<string name="common_poll_summary">"Poll: %1$s"</string>
|
<string name="common_poll_summary">"Poll: %1$s"</string>
|
||||||
<string name="dialog_title_confirmation">"Confirmation"</string>
|
<string name="dialog_title_confirmation">"Confirmation"</string>
|
||||||
<string name="dialog_title_warning">"Warning"</string>
|
<string name="dialog_title_warning">"Warning"</string>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:09440b685e219b4a5f5dc78d2694707d7eeb1905d6fdea6395e3dcc941482ac5
|
||||||
|
size 51846
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ae8d6b561d358c7ebccae2da4281f3ea1b4d433f52de3c3b2008c1208c0c8bd7
|
||||||
|
size 54037
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a0cd0936ace9bac190f21680bf339cc23839e5f5d241522cb8f328b2db0887f6
|
||||||
|
size 48293
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:cc96f4b704396e0931a08a968139ee9d586cd1e14358b546361d11bc91e7bab5
|
||||||
|
size 50373
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue