Merge branch 'develop' of github.com:vector-im/element-x-android into develop
This commit is contained in:
commit
0851e14855
34 changed files with 250 additions and 115 deletions
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)
|
this.timelineItems.emit(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildAndCacheItem(
|
private suspend fun buildAndCacheItem(
|
||||||
timelineItems: List<MatrixTimelineItem>,
|
timelineItems: List<MatrixTimelineItem>,
|
||||||
index: Int
|
index: Int
|
||||||
): TimelineItem? {
|
): TimelineItem? {
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class TimelineItemContentFactory @Inject constructor(
|
||||||
private val failedToParseStateFactory: TimelineItemContentFailedToParseStateFactory
|
private val failedToParseStateFactory: TimelineItemContentFailedToParseStateFactory
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent {
|
suspend fun create(eventTimelineItem: EventTimelineItem): TimelineItemEventContent {
|
||||||
return when (val itemContent = eventTimelineItem.content) {
|
return when (val itemContent = eventTimelineItem.content) {
|
||||||
is FailedToParseMessageLikeContent -> failedToParseMessageFactory.create(itemContent)
|
is FailedToParseMessageLikeContent -> failedToParseMessageFactory.create(itemContent)
|
||||||
is FailedToParseStateContent -> failedToParseStateFactory.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.TimelineItemEventContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
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.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.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.poll.PollKind
|
import io.element.android.libraries.matrix.api.poll.PollKind
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
|
||||||
|
|
@ -26,9 +29,12 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class TimelineItemContentPollFactory @Inject constructor(
|
class TimelineItemContentPollFactory @Inject constructor(
|
||||||
private val matrixClient: MatrixClient,
|
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
|
// Todo Move this computation to the matrix rust sdk
|
||||||
val showResults = content.kind == PollKind.Disclosed && matrixClient.sessionId in content.votes.flatMap { it.value }
|
val showResults = content.kind == PollKind.Disclosed && matrixClient.sessionId in content.votes.flatMap { it.value }
|
||||||
val pollVotesCount = content.votes.flatMap { it.value }.size
|
val pollVotesCount = content.votes.flatMap { it.value }.size
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class TimelineItemEventFactory @Inject constructor(
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun create(
|
suspend fun create(
|
||||||
currentTimelineItem: MatrixTimelineItem.Event,
|
currentTimelineItem: MatrixTimelineItem.Event,
|
||||||
index: Int,
|
index: Int,
|
||||||
timelineItems: List<MatrixTimelineItem>,
|
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.androidutils.filesize.FakeFileSizeFormatter
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter
|
import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter
|
||||||
import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
|
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.api.timeline.item.event.EventTimelineItem
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
|
|
@ -52,14 +53,14 @@ internal fun TestScope.aTimelineItemsFactory(): TimelineItemsFactory {
|
||||||
messageFactory = TimelineItemContentMessageFactory(FakeFileSizeFormatter(), FileExtensionExtractorWithoutValidation()),
|
messageFactory = TimelineItemContentMessageFactory(FakeFileSizeFormatter(), FileExtensionExtractorWithoutValidation()),
|
||||||
redactedMessageFactory = TimelineItemContentRedactedFactory(),
|
redactedMessageFactory = TimelineItemContentRedactedFactory(),
|
||||||
stickerFactory = TimelineItemContentStickerFactory(),
|
stickerFactory = TimelineItemContentStickerFactory(),
|
||||||
pollFactory = TimelineItemContentPollFactory(matrixClient),
|
pollFactory = TimelineItemContentPollFactory(matrixClient, FakeFeatureFlagService()),
|
||||||
pollEndFactory = TimelineItemContentPollEndFactory(),
|
pollEndFactory = TimelineItemContentPollEndFactory(),
|
||||||
utdFactory = TimelineItemContentUTDFactory(),
|
utdFactory = TimelineItemContentUTDFactory(),
|
||||||
roomMembershipFactory = TimelineItemContentRoomMembershipFactory(timelineEventFormatter),
|
roomMembershipFactory = TimelineItemContentRoomMembershipFactory(timelineEventFormatter),
|
||||||
profileChangeFactory = TimelineItemContentProfileChangeFactory(timelineEventFormatter),
|
profileChangeFactory = TimelineItemContentProfileChangeFactory(timelineEventFormatter),
|
||||||
stateFactory = TimelineItemContentStateFactory(timelineEventFormatter),
|
stateFactory = TimelineItemContentStateFactory(timelineEventFormatter),
|
||||||
failedToParseMessageFactory = TimelineItemContentFailedToParseMessageFactory(),
|
failedToParseMessageFactory = TimelineItemContentFailedToParseMessageFactory(),
|
||||||
failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory()
|
failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory(),
|
||||||
),
|
),
|
||||||
matrixClient = matrixClient,
|
matrixClient = matrixClient,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,7 @@ class DeveloperSettingsPresenter @Inject constructor(
|
||||||
FeatureUiModel(
|
FeatureUiModel(
|
||||||
key = feature.key,
|
key = feature.key,
|
||||||
title = feature.title,
|
title = feature.title,
|
||||||
|
description = feature.description,
|
||||||
isEnabled = isEnabled
|
isEnabled = isEnabled
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package io.element.android.libraries.designsystem.atomic.atoms
|
package io.element.android.libraries.designsystem.atomic.atoms
|
||||||
|
|
||||||
import android.graphics.BlurMaskFilter
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
|
|
@ -27,24 +26,16 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.blur
|
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.drawBehind
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.geometry.CornerRadius
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.geometry.Rect
|
|
||||||
import androidx.compose.ui.geometry.RoundRect
|
|
||||||
import androidx.compose.ui.graphics.ClipOp
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Paint
|
|
||||||
import androidx.compose.ui.graphics.Path
|
|
||||||
import androidx.compose.ui.graphics.drawscope.clipPath
|
|
||||||
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
|
||||||
import androidx.compose.ui.graphics.toArgb
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.libraries.designsystem.R
|
import io.element.android.libraries.designsystem.R
|
||||||
|
import io.element.android.libraries.designsystem.modifiers.blurCompat
|
||||||
|
import io.element.android.libraries.designsystem.modifiers.blurredShapeShadow
|
||||||
|
import io.element.android.libraries.designsystem.modifiers.canUseBlurMaskFilter
|
||||||
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.theme.ElementTheme
|
import io.element.android.libraries.theme.ElementTheme
|
||||||
|
|
@ -53,6 +44,7 @@ import io.element.android.libraries.theme.ElementTheme
|
||||||
fun ElementLogoAtom(
|
fun ElementLogoAtom(
|
||||||
size: ElementLogoAtomSize,
|
size: ElementLogoAtomSize,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
useBlurredShadow: Boolean = canUseBlurMaskFilter(),
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
) {
|
) {
|
||||||
val blur = if (darkTheme) 160.dp else 24.dp
|
val blur = if (darkTheme) 160.dp else 24.dp
|
||||||
|
|
@ -66,22 +58,35 @@ fun ElementLogoAtom(
|
||||||
.border(size.borderWidth, borderColor, RoundedCornerShape(size.cornerRadius)),
|
.border(size.borderWidth, borderColor, RoundedCornerShape(size.cornerRadius)),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Box(
|
if (useBlurredShadow) {
|
||||||
Modifier
|
Box(
|
||||||
.size(size.outerSize)
|
Modifier
|
||||||
.shapeShadow(
|
.size(size.outerSize)
|
||||||
color = shadowColor,
|
.blurredShapeShadow(
|
||||||
cornerRadius = size.cornerRadius,
|
color = shadowColor,
|
||||||
blurRadius = size.shadowRadius,
|
cornerRadius = size.cornerRadius,
|
||||||
offsetY = 8.dp,
|
blurRadius = size.shadowRadius,
|
||||||
)
|
offsetY = 8.dp,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.size(size.outerSize)
|
||||||
|
.shadow(
|
||||||
|
elevation = size.shadowRadius,
|
||||||
|
shape = RoundedCornerShape(size.cornerRadius),
|
||||||
|
clip = false,
|
||||||
|
ambientColor = shadowColor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.clip(RoundedCornerShape(size.cornerRadius))
|
.clip(RoundedCornerShape(size.cornerRadius))
|
||||||
.size(size.outerSize)
|
.size(size.outerSize)
|
||||||
.background(backgroundColor)
|
.background(backgroundColor)
|
||||||
.blur(blur)
|
.blurCompat(blur)
|
||||||
)
|
)
|
||||||
Image(
|
Image(
|
||||||
modifier = Modifier.size(size.logoSize),
|
modifier = Modifier.size(size.logoSize),
|
||||||
|
|
@ -121,44 +126,6 @@ sealed class ElementLogoAtomSize(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Modifier.shapeShadow(
|
|
||||||
color: Color = Color.Black,
|
|
||||||
cornerRadius: Dp = 0.dp,
|
|
||||||
offsetX: Dp = 0.dp,
|
|
||||||
offsetY: Dp = 0.dp,
|
|
||||||
blurRadius: Dp = 0.dp,
|
|
||||||
) = then(
|
|
||||||
drawBehind {
|
|
||||||
drawIntoCanvas { canvas ->
|
|
||||||
val path = Path().apply {
|
|
||||||
addRoundRect(RoundRect(Rect(Offset.Zero, size), CornerRadius(cornerRadius.toPx())))
|
|
||||||
}
|
|
||||||
|
|
||||||
clipPath(path, ClipOp.Difference) {
|
|
||||||
val paint = Paint()
|
|
||||||
val frameworkPaint = paint.asFrameworkPaint()
|
|
||||||
if (blurRadius != 0.dp) {
|
|
||||||
frameworkPaint.maskFilter = BlurMaskFilter(blurRadius.toPx(), BlurMaskFilter.Blur.NORMAL)
|
|
||||||
}
|
|
||||||
frameworkPaint.color = color.toArgb()
|
|
||||||
|
|
||||||
val leftPixel = offsetX.toPx()
|
|
||||||
val topPixel = offsetY.toPx()
|
|
||||||
val rightPixel = size.width + topPixel
|
|
||||||
val bottomPixel = size.height + leftPixel
|
|
||||||
|
|
||||||
canvas.drawRect(
|
|
||||||
left = leftPixel,
|
|
||||||
top = topPixel,
|
|
||||||
right = rightPixel,
|
|
||||||
bottom = bottomPixel,
|
|
||||||
paint = paint,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@DayNightPreviews
|
@DayNightPreviews
|
||||||
internal fun ElementLogoAtomMediumPreview() {
|
internal fun ElementLogoAtomMediumPreview() {
|
||||||
|
|
@ -172,7 +139,19 @@ internal fun ElementLogoAtomLargePreview() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContentToPreview(elementLogoAtomSize: ElementLogoAtomSize) {
|
@DayNightPreviews
|
||||||
|
internal fun ElementLogoAtomMediumNoBlurShadowPreview() {
|
||||||
|
ContentToPreview(ElementLogoAtomSize.Medium, useBlurredShadow = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@DayNightPreviews
|
||||||
|
internal fun ElementLogoAtomLargeNoBlurShadowPreview() {
|
||||||
|
ContentToPreview(ElementLogoAtomSize.Large, useBlurredShadow = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ContentToPreview(elementLogoAtomSize: ElementLogoAtomSize, useBlurredShadow: Boolean = true) {
|
||||||
ElementPreview {
|
ElementPreview {
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -180,7 +159,7 @@ private fun ContentToPreview(elementLogoAtomSize: ElementLogoAtomSize) {
|
||||||
.background(ElementTheme.colors.bgSubtlePrimary),
|
.background(ElementTheme.colors.bgSubtlePrimary),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
ElementLogoAtom(elementLogoAtomSize)
|
ElementLogoAtom(elementLogoAtomSize, useBlurredShadow = useBlurredShadow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
package io.element.android.libraries.designsystem.components.preferences
|
package io.element.android.libraries.designsystem.components.preferences
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
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.Row
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.Checkbox
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.toEnabledColor
|
import io.element.android.libraries.designsystem.toEnabledColor
|
||||||
|
import io.element.android.libraries.designsystem.toSecondaryEnabledColor
|
||||||
import io.element.android.libraries.theme.ElementTheme
|
import io.element.android.libraries.theme.ElementTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -42,6 +45,7 @@ fun PreferenceCheckbox(
|
||||||
title: String,
|
title: String,
|
||||||
isChecked: Boolean,
|
isChecked: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
supportingText: String? = null,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
icon: ImageVector? = null,
|
icon: ImageVector? = null,
|
||||||
showIconAreaIfNoIcon: Boolean = false,
|
showIconAreaIfNoIcon: Boolean = false,
|
||||||
|
|
@ -60,13 +64,23 @@ fun PreferenceCheckbox(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
isVisible = showIconAreaIfNoIcon
|
isVisible = showIconAreaIfNoIcon
|
||||||
)
|
)
|
||||||
Text(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(1f),
|
||||||
.weight(1f),
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
style = ElementTheme.typography.fontBodyLgRegular,
|
) {
|
||||||
text = title,
|
Text(
|
||||||
color = enabled.toEnabledColor(),
|
style = ElementTheme.typography.fontBodyLgRegular,
|
||||||
)
|
text = title,
|
||||||
|
color = enabled.toEnabledColor(),
|
||||||
|
)
|
||||||
|
if (supportingText != null) {
|
||||||
|
Text(
|
||||||
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
|
text = supportingText,
|
||||||
|
color = enabled.toSecondaryEnabledColor(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Checkbox(
|
Checkbox(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.CenterVertically),
|
.align(Alignment.CenterVertically),
|
||||||
|
|
@ -83,10 +97,19 @@ internal fun PreferenceCheckboxPreview() = ElementThemedPreview { ContentToPrevi
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContentToPreview() {
|
private fun ContentToPreview() {
|
||||||
PreferenceCheckbox(
|
Column {
|
||||||
title = "Checkbox",
|
PreferenceCheckbox(
|
||||||
icon = Icons.Default.Announcement,
|
title = "Checkbox",
|
||||||
enabled = true,
|
icon = Icons.Default.Announcement,
|
||||||
isChecked = true
|
enabled = true,
|
||||||
)
|
isChecked = true
|
||||||
|
)
|
||||||
|
PreferenceCheckbox(
|
||||||
|
title = "Checkbox with supporting text",
|
||||||
|
supportingText = "Supporting text",
|
||||||
|
icon = Icons.Default.Announcement,
|
||||||
|
enabled = true,
|
||||||
|
isChecked = true
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.designsystem.modifiers
|
||||||
|
|
||||||
|
import android.graphics.BlurMaskFilter
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.ChecksSdkIntAtLeast
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.composed
|
||||||
|
import androidx.compose.ui.draw.BlurredEdgeTreatment
|
||||||
|
import androidx.compose.ui.draw.blur
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Rect
|
||||||
|
import androidx.compose.ui.geometry.RoundRect
|
||||||
|
import androidx.compose.ui.graphics.ClipOp
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Paint
|
||||||
|
import androidx.compose.ui.graphics.Path
|
||||||
|
import androidx.compose.ui.graphics.drawscope.clipPath
|
||||||
|
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the blur modifier is supported on the current OS version.
|
||||||
|
*
|
||||||
|
* The docs say the `blur` modifier is only supported on Android 12+:
|
||||||
|
* https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier#(androidx.compose.ui.Modifier).blur(androidx.compose.ui.unit.Dp,androidx.compose.ui.draw.BlurredEdgeTreatment)
|
||||||
|
* */
|
||||||
|
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S)
|
||||||
|
fun canUseBlur(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun canUseBlurMaskFilter() = !LocalView.current.isHardwareAccelerated
|
||||||
|
|
||||||
|
fun Modifier.blurredShapeShadow(
|
||||||
|
color: Color = Color.Black,
|
||||||
|
cornerRadius: Dp = 0.dp,
|
||||||
|
offsetX: Dp = 0.dp,
|
||||||
|
offsetY: Dp = 0.dp,
|
||||||
|
blurRadius: Dp = 0.dp,
|
||||||
|
) = then(
|
||||||
|
drawBehind {
|
||||||
|
drawIntoCanvas { canvas ->
|
||||||
|
val path = Path().apply {
|
||||||
|
addRoundRect(RoundRect(Rect(Offset.Zero, size), CornerRadius(cornerRadius.toPx())))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the blurred shadow, then cut out the shape from it
|
||||||
|
clipPath(path, ClipOp.Difference) {
|
||||||
|
val paint = Paint()
|
||||||
|
val frameworkPaint = paint.asFrameworkPaint()
|
||||||
|
if (blurRadius != 0.dp) {
|
||||||
|
frameworkPaint.maskFilter = BlurMaskFilter(blurRadius.toPx(), BlurMaskFilter.Blur.NORMAL)
|
||||||
|
}
|
||||||
|
frameworkPaint.color = color.toArgb()
|
||||||
|
|
||||||
|
val leftPixel = offsetX.toPx()
|
||||||
|
val topPixel = offsetY.toPx()
|
||||||
|
val rightPixel = size.width + topPixel
|
||||||
|
val bottomPixel = size.height + leftPixel
|
||||||
|
|
||||||
|
canvas.drawRect(
|
||||||
|
left = leftPixel,
|
||||||
|
top = topPixel,
|
||||||
|
right = rightPixel,
|
||||||
|
bottom = bottomPixel,
|
||||||
|
paint = paint,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Modifier.blurCompat(
|
||||||
|
radius: Dp,
|
||||||
|
edgeTreatment: BlurredEdgeTreatment = BlurredEdgeTreatment.Rectangle
|
||||||
|
): Modifier = composed {
|
||||||
|
when {
|
||||||
|
radius.value == 0f -> this
|
||||||
|
canUseBlur() -> blur(radius, edgeTreatment)
|
||||||
|
else -> this // Added in case we find a way to make this work on older devices
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,5 +25,11 @@ enum class FeatureFlags(
|
||||||
LocationSharing(
|
LocationSharing(
|
||||||
key = "feature.locationsharing",
|
key = "feature.locationsharing",
|
||||||
title = "Allow user to share location",
|
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) {
|
return if (feature is FeatureFlags) {
|
||||||
when (feature) {
|
when (feature) {
|
||||||
FeatureFlags.LocationSharing -> true
|
FeatureFlags.LocationSharing -> true
|
||||||
|
FeatureFlags.Polls -> false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ fun FeaturePreferenceView(
|
||||||
) {
|
) {
|
||||||
PreferenceCheckbox(
|
PreferenceCheckbox(
|
||||||
title = feature.title,
|
title = feature.title,
|
||||||
|
supportingText = feature.description,
|
||||||
isChecked = feature.isEnabled,
|
isChecked = feature.isEnabled,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onCheckedChange = onCheckedChange
|
onCheckedChange = onCheckedChange
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,6 @@ package io.element.android.libraries.featureflag.ui.model
|
||||||
data class FeatureUiModel(
|
data class FeatureUiModel(
|
||||||
val key: String,
|
val key: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
|
val description: String?,
|
||||||
val isEnabled: Boolean
|
val isEnabled: Boolean
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
fun aFeatureUiModelList(): ImmutableList<FeatureUiModel> {
|
fun aFeatureUiModelList(): ImmutableList<FeatureUiModel> {
|
||||||
return persistentListOf(
|
return persistentListOf(
|
||||||
FeatureUiModel("key1", "Display State Events", true),
|
FeatureUiModel("key1", "Display State Events", "Show state events in the timeline", true),
|
||||||
FeatureUiModel("key2", "Display Room Events", false)
|
FeatureUiModel("key2", "Display Room Events", null, false),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6d2eab27a1132893542b240399a4003b4049b72903a822077f69dd9dafd26f71
|
oid sha256:2cc8d498816a04c537ad2835068a2badab656aa723a900b471c8f79320d7967c
|
||||||
size 299106
|
size 300473
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:fbad2f74e329a424e75ef96cb0c427a7baba044f3267346756324c39ebd6add6
|
oid sha256:634dc94c58f6e7aed8abf9c11f33e81a65e81c439b247321a398e3728d5696d4
|
||||||
size 404196
|
size 401397
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:56f53e2c13480d5f31d38c671612c81fa802e1f35a55ada99d544917f01b91ba
|
oid sha256:4bf8563c80e874ff79850e37bbf71609aac72c62c74cbe8255e403bd00feadc3
|
||||||
size 338952
|
size 326514
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:cfe3663daab250bcd733ab746e4864472691b40cf927a3cd73d15b323dec2b68
|
oid sha256:45984b598d30a2511e03644927a9f1f34b4173f2eebb8a328a19df8643385f18
|
||||||
size 329945
|
size 317243
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c449e6e4162f12a6be7faa668b930b294489ae217c2a7493f5671145a926079e
|
oid sha256:1d96057db48fd7e3461fde86b84c09688c0af49e51bcd94affa2df9c9785b6f5
|
||||||
size 340944
|
size 327755
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:0355120442254880360b982df6c2c7cce84a471f632436c96e79ffafed35de14
|
oid sha256:4007453c80be5700ec9942983ef903a2404e7e3879d82c189a5e95687916e8eb
|
||||||
size 324378
|
size 310597
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5d05151967955c961ca47619de2f28f86d0350299b61fbef0371c1706439a844
|
oid sha256:5b07c105d41caf87c34a506138ff5d43df853d9b7b7143e0099f222886ca217e
|
||||||
size 421908
|
size 420316
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3a2056882e65afe8fc2d0dc60efc7912ca5674acb57e3690136a8704f9f063ed
|
oid sha256:738ee59035ecbc006ccd43ff10ce847d6d741da780b3e189c221b55a1b6ec06c
|
||||||
size 407942
|
size 405327
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6521d409bc1fc70fa422ffec400d9f20d0b7fe16adebb7f5b0a9e5f135328ffc
|
oid sha256:1b3ba2db52bd7ee1713ba1b276b0e3a4a7edf406a8833027d548c9fdf4dbe838
|
||||||
size 422213
|
size 419123
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d39cfdb0db12a6d14f3105aa168ea002a845d2083f07ecfabe0858739507018d
|
oid sha256:4bd69417650f09e240405d839464ba716d55d01e6230a9aa8b923460b4c88443
|
||||||
size 396789
|
size 393093
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4a9a3902dd33ab3e528fa9c534302228113cc468914025766a21397ad3eda9e2
|
oid sha256:aa65609944cd9118668fcdf75a7882111230cceebbdc68bab764f51929dfc6f2
|
||||||
size 45064
|
size 49584
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4a9a3902dd33ab3e528fa9c534302228113cc468914025766a21397ad3eda9e2
|
oid sha256:aa65609944cd9118668fcdf75a7882111230cceebbdc68bab764f51929dfc6f2
|
||||||
size 45064
|
size 49584
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:cd1cc710443af62efddf0ea165a6289912f471f669803255368d3266b872f5f6
|
oid sha256:c571049a9cbf3dc13d8a700aa4d3639f8fca89db749c5ebfbb59ca6a0bb0b9eb
|
||||||
size 49829
|
size 54206
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:cd1cc710443af62efddf0ea165a6289912f471f669803255368d3266b872f5f6
|
oid sha256:c571049a9cbf3dc13d8a700aa4d3639f8fca89db749c5ebfbb59ca6a0bb0b9eb
|
||||||
size 49829
|
size 54206
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:3f76220968b3e28b2c1ddd91b4036bf037a98e97c859e8616314ed953862cc64
|
||||||
|
size 42525
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:3321dd27c1fbe47df862dad9b591faf9279da1ea4eb66b7b900d8c962280a74e
|
||||||
|
size 31811
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ca4e1e3cd5d7cb9fd8416bc7074272b759bbc501993829b6edef6809cf519aee
|
||||||
|
size 27532
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:8615ceb43c29becd4cfe6062d8fc844b15b717e8f2e5d7b151bdc4478de59381
|
||||||
|
size 20607
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:06813451633c48fccac6f70810ecb9465946b643376fccd61312b6064b0a3a40
|
oid sha256:7ba8a794a8b65f412806ee1accc4f8678b046e880fd76c038eb054155ba514ea
|
||||||
size 11518
|
size 28463
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue