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:
1
changelog.d/1064.wip
Normal file
1
changelog.d/1064.wip
Normal file
@@ -0,0 +1 @@
|
||||
[Poll] Add feature flag in developer options
|
||||
@@ -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? {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -110,6 +110,7 @@ class DeveloperSettingsPresenter @Inject constructor(
|
||||
FeatureUiModel(
|
||||
key = feature.key,
|
||||
title = feature.title,
|
||||
description = feature.description,
|
||||
isEnabled = isEnabled
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class BuildtimeFeatureFlagProvider @Inject constructor() :
|
||||
return if (feature is FeatureFlags) {
|
||||
when (feature) {
|
||||
FeatureFlags.LocationSharing -> true
|
||||
FeatureFlags.Polls -> false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
|
||||
@@ -54,6 +54,7 @@ fun FeaturePreferenceView(
|
||||
) {
|
||||
PreferenceCheckbox(
|
||||
title = feature.title,
|
||||
supportingText = feature.description,
|
||||
isChecked = feature.isEnabled,
|
||||
modifier = modifier,
|
||||
onCheckedChange = onCheckedChange
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user