Add feature flag for polls (#1064)

* 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

* Add feature flag for polls

* Add supporting text in PreferenceCheckbox

* Render poll events if feature flag is enabled

* changelog

* Update screenshots

* Fix tests

* Move feature flag check to poll factory

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Florian Renaud
2023-08-16 17:14:38 +02:00
committed by GitHub
parent 9cb2424101
commit 1083b431b6
18 changed files with 72 additions and 31 deletions

1
changelog.d/1064.wip Normal file
View File

@@ -0,0 +1 @@
[Poll] Add feature flag in developer options

View File

@@ -89,7 +89,7 @@ class TimelineItemsFactory @Inject constructor(
this.timelineItems.emit(result)
}
private fun buildAndCacheItem(
private suspend fun buildAndCacheItem(
timelineItems: List<MatrixTimelineItem>,
index: Int
): TimelineItem? {

View File

@@ -47,7 +47,7 @@ class TimelineItemContentFactory @Inject constructor(
private val failedToParseStateFactory: TimelineItemContentFailedToParseStateFactory
) {
fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent {
suspend fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent {
return when (val itemContent = eventTimelineItem.content) {
is FailedToParseMessageLikeContent -> failedToParseMessageFactory.create(itemContent)
is FailedToParseStateContent -> failedToParseStateFactory.create(itemContent)

View File

@@ -18,7 +18,10 @@ package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
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.poll.PollKind
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
@@ -26,9 +29,12 @@ import javax.inject.Inject
class TimelineItemContentPollFactory @Inject constructor(
private val matrixClient: MatrixClient,
private val featureFlagService: FeatureFlagService,
) {
fun create(content: PollContent): TimelineItemEventContent {
suspend fun create(content: PollContent): TimelineItemEventContent {
if (!featureFlagService.isFeatureEnabled(FeatureFlags.Polls)) return TimelineItemUnknownContent
// Todo Move this computation to the matrix rust sdk
val showResults = content.kind == PollKind.Disclosed && matrixClient.sessionId in content.votes.flatMap { it.value }
val pollVotesCount = content.votes.flatMap { it.value }.size

View File

@@ -38,7 +38,7 @@ class TimelineItemEventFactory @Inject constructor(
private val matrixClient: MatrixClient,
) {
fun create(
suspend fun create(
currentTimelineItem: MatrixTimelineItem.Event,
index: Int,
timelineItems: List<MatrixTimelineItem>,

View File

@@ -37,6 +37,7 @@ import io.element.android.features.messages.impl.timeline.util.FileExtensionExtr
import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter
import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter
import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.tests.testutils.testCoroutineDispatchers
@@ -52,14 +53,14 @@ internal fun TestScope.aTimelineItemsFactory(): TimelineItemsFactory {
messageFactory = TimelineItemContentMessageFactory(FakeFileSizeFormatter(), FileExtensionExtractorWithoutValidation()),
redactedMessageFactory = TimelineItemContentRedactedFactory(),
stickerFactory = TimelineItemContentStickerFactory(),
pollFactory = TimelineItemContentPollFactory(matrixClient),
pollFactory = TimelineItemContentPollFactory(matrixClient, FakeFeatureFlagService()),
pollEndFactory = TimelineItemContentPollEndFactory(),
utdFactory = TimelineItemContentUTDFactory(),
roomMembershipFactory = TimelineItemContentRoomMembershipFactory(timelineEventFormatter),
profileChangeFactory = TimelineItemContentProfileChangeFactory(timelineEventFormatter),
stateFactory = TimelineItemContentStateFactory(timelineEventFormatter),
failedToParseMessageFactory = TimelineItemContentFailedToParseMessageFactory(),
failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory()
failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory(),
),
matrixClient = matrixClient,
),

View File

@@ -110,6 +110,7 @@ class DeveloperSettingsPresenter @Inject constructor(
FeatureUiModel(
key = feature.key,
title = feature.title,
description = feature.description,
isEnabled = isEnabled
)
}

View File

@@ -17,6 +17,8 @@
package io.element.android.libraries.designsystem.components.preferences
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -35,6 +37,7 @@ import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Checkbox
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.toEnabledColor
import io.element.android.libraries.designsystem.toSecondaryEnabledColor
import io.element.android.libraries.theme.ElementTheme
@Composable
@@ -42,6 +45,7 @@ fun PreferenceCheckbox(
title: String,
isChecked: Boolean,
modifier: Modifier = Modifier,
supportingText: String? = null,
enabled: Boolean = true,
icon: ImageVector? = null,
showIconAreaIfNoIcon: Boolean = false,
@@ -60,13 +64,23 @@ fun PreferenceCheckbox(
enabled = enabled,
isVisible = showIconAreaIfNoIcon
)
Text(
modifier = Modifier
.weight(1f),
style = ElementTheme.typography.fontBodyLgRegular,
text = title,
color = enabled.toEnabledColor(),
)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
style = ElementTheme.typography.fontBodyLgRegular,
text = title,
color = enabled.toEnabledColor(),
)
if (supportingText != null) {
Text(
style = ElementTheme.typography.fontBodyMdRegular,
text = supportingText,
color = enabled.toSecondaryEnabledColor(),
)
}
}
Checkbox(
modifier = Modifier
.align(Alignment.CenterVertically),
@@ -83,10 +97,19 @@ internal fun PreferenceCheckboxPreview() = ElementThemedPreview { ContentToPrevi
@Composable
private fun ContentToPreview() {
PreferenceCheckbox(
title = "Checkbox",
icon = Icons.Default.Announcement,
enabled = true,
isChecked = true
)
Column {
PreferenceCheckbox(
title = "Checkbox",
icon = Icons.Default.Announcement,
enabled = true,
isChecked = true
)
PreferenceCheckbox(
title = "Checkbox with supporting text",
supportingText = "Supporting text",
icon = Icons.Default.Announcement,
enabled = true,
isChecked = true
)
}
}

View File

@@ -25,5 +25,11 @@ enum class FeatureFlags(
LocationSharing(
key = "feature.locationsharing",
title = "Allow user to share location",
),
Polls(
key = "feature.polls",
title = "Polls",
description = "Render poll events in the timeline",
defaultValue = false,
)
}

View File

@@ -30,6 +30,7 @@ class BuildtimeFeatureFlagProvider @Inject constructor() :
return if (feature is FeatureFlags) {
when (feature) {
FeatureFlags.LocationSharing -> true
FeatureFlags.Polls -> false
}
} else {
false

View File

@@ -54,6 +54,7 @@ fun FeaturePreferenceView(
) {
PreferenceCheckbox(
title = feature.title,
supportingText = feature.description,
isChecked = feature.isEnabled,
modifier = modifier,
onCheckedChange = onCheckedChange

View File

@@ -19,5 +19,6 @@ package io.element.android.libraries.featureflag.ui.model
data class FeatureUiModel(
val key: String,
val title: String,
val description: String?,
val isEnabled: Boolean
)

View File

@@ -21,7 +21,7 @@ import kotlinx.collections.immutable.persistentListOf
fun aFeatureUiModelList(): ImmutableList<FeatureUiModel> {
return persistentListOf(
FeatureUiModel("key1", "Display State Events", true),
FeatureUiModel("key2", "Display Room Events", false)
FeatureUiModel("key1", "Display State Events", "Show state events in the timeline", true),
FeatureUiModel("key2", "Display Room Events", null, false),
)
}