Merge pull request #4926 from element-hq/feature/bma/roomListHeader

Remove bloom effect and replace by linear gradient
This commit is contained in:
Benoit Marty 2025-07-08 10:12:31 +02:00 committed by GitHub
commit f794117047
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
305 changed files with 893 additions and 1436 deletions

View file

@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
@ -22,7 +21,6 @@ import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@ -30,14 +28,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import io.element.android.appconfig.RoomListConfig
import io.element.android.compound.theme.ElementTheme
@ -51,12 +44,10 @@ import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.components.avatarBloom
import io.element.android.libraries.designsystem.modifiers.backgroundVerticalGradient
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.applyScaleDown
import io.element.android.libraries.designsystem.text.roundToPx
import io.element.android.libraries.designsystem.text.toDp
import io.element.android.libraries.designsystem.text.toSp
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.theme.components.DropdownMenu
@ -73,8 +64,6 @@ import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings
private val avatarBloomSize = 430.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RoomListTopBar(
@ -126,25 +115,13 @@ private fun DefaultRoomListTopBar(
canReportBug: Boolean,
modifier: Modifier = Modifier,
) {
// We need this to manually clip the top app bar in preview mode
val previewAppBarHeight = if (LocalInspectionMode.current) {
112.dp.roundToPx()
} else {
null
}
val collapsedFraction = scrollBehavior.state.collapsedFraction
var appBarHeight by remember {
mutableIntStateOf(previewAppBarHeight ?: 0)
}
val avatarData by remember(matrixUser) {
derivedStateOf {
matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar)
}
}
val statusBarPadding = with(LocalDensity.current) { WindowInsets.statusBars.getTop(this).toDp() }
Box(modifier = modifier) {
val collapsedTitleTextStyle = ElementTheme.typography.aliasScreenTitle
val expandedTitleTextStyle = ElementTheme.typography.fontHeadingLgBold.copy(
@ -160,40 +137,13 @@ private fun DefaultRoomListTopBar(
titleLarge = collapsedTitleTextStyle
),
) {
Column(
modifier = Modifier
.onSizeChanged {
appBarHeight = it.height
}
.avatarBloom(
avatarData = avatarData,
background = if (ElementTheme.isLightTheme) {
// Workaround to display a very subtle bloom for avatars with very soft colors
Color(0xFFF9F9F9)
} else {
ElementTheme.colors.bgCanvasDefault
},
blurSize = DpSize(avatarBloomSize, avatarBloomSize),
offset = DpOffset(24.dp, 24.dp + statusBarPadding),
clipToSize = if (appBarHeight > 0) {
DpSize(
avatarBloomSize,
appBarHeight.toDp()
)
} else {
DpSize.Unspecified
},
bottomSoftEdgeColor = ElementTheme.colors.bgCanvasDefault,
bottomSoftEdgeAlpha = if (displayFilters) {
1f
} else {
1f - collapsedFraction
},
alpha = if (areSearchResultsDisplayed) 0f else 1f,
)
.statusBarsPadding(),
) {
Column {
MediumTopAppBar(
modifier = Modifier
.backgroundVerticalGradient(
isVisible = !areSearchResultsDisplayed,
)
.statusBarsPadding(),
colors = TopAppBarDefaults.mediumTopAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = Color.Transparent,

View file

@ -37,11 +37,11 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
import io.element.android.libraries.designsystem.modifiers.subtleColorStops
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.toPx
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
import io.element.android.libraries.designsystem.theme.highlightedMessageBackgroundColor
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.ui.strings.CommonStrings
@ -206,23 +206,20 @@ internal fun TimelineItemRow(
@Suppress("ModifierComposable")
@Composable
private fun Modifier.focusedEvent(
focusedEventOffset: Dp
focusedEventOffset: Dp,
isEnterpriseBuild: Boolean = LocalBuildMeta.current.isEnterpriseBuild,
): Modifier {
val highlightedLineColor = ElementTheme.colors.textActionAccent
val gradientFirstColor = if (LocalBuildMeta.current.isEnterpriseBuild) {
ElementTheme.colors.textActionAccent.copy(alpha = 0.125f)
val highlightedLineColor = if (isEnterpriseBuild) {
ElementTheme.colors.textActionAccent
} else {
ElementTheme.colors.highlightedMessageBackgroundColor
ElementTheme.colors.borderAccentSubtle
}
val gradientColors = listOf(
gradientFirstColor,
ElementTheme.colors.bgCanvasDefault,
)
val gradientColors = subtleColorStops(isEnterpriseBuild)
val verticalOffset = focusedEventOffset.toPx()
val verticalRatio = 0.7f
return drawWithCache {
val brush = Brush.verticalGradient(
colors = gradientColors,
colorStops = gradientColors,
endY = size.height * verticalRatio,
)
onDrawBehind {
@ -251,3 +248,18 @@ internal fun FocusedEventPreview() = ElementPreview {
.focusedEvent(0.dp),
)
}
@PreviewsDayNight
@Composable
internal fun FocusedEventEnterprisePreview() = ElementPreview {
Box(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.height(160.dp)
.focusedEvent(
focusedEventOffset = 0.dp,
isEnterpriseBuild = true,
),
)
}

View file

@ -14,10 +14,6 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.components.Badge
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.badgeNegativeBackgroundColor
import io.element.android.libraries.designsystem.theme.badgeNegativeContentColor
import io.element.android.libraries.designsystem.theme.badgeNeutralBackgroundColor
import io.element.android.libraries.designsystem.theme.badgeNeutralContentColor
object MatrixBadgeAtom {
data class MatrixBadgeData(
@ -39,21 +35,21 @@ object MatrixBadgeAtom {
) {
val backgroundColor = when (data.type) {
Type.Positive -> ElementTheme.colors.bgBadgeAccent
Type.Neutral -> ElementTheme.colors.badgeNeutralBackgroundColor
Type.Negative -> ElementTheme.colors.badgeNegativeBackgroundColor
Type.Neutral -> ElementTheme.colors.bgBadgeDefault
Type.Negative -> ElementTheme.colors.bgCriticalSubtle
Type.Info -> ElementTheme.colors.bgBadgeInfo
}
val textColor = when (data.type) {
Type.Positive -> ElementTheme.colors.textBadgeAccent
Type.Neutral -> ElementTheme.colors.badgeNeutralContentColor
Type.Negative -> ElementTheme.colors.badgeNegativeContentColor
Type.Neutral -> ElementTheme.colors.textPrimary
Type.Negative -> ElementTheme.colors.textCriticalPrimary
Type.Info -> ElementTheme.colors.textBadgeInfo
}
val iconColor = when (data.type) {
Type.Positive -> ElementTheme.colors.textBadgeAccent
Type.Neutral -> ElementTheme.colors.iconSecondary
Type.Positive -> ElementTheme.colors.iconAccentPrimary
Type.Neutral -> ElementTheme.colors.iconPrimary
Type.Negative -> ElementTheme.colors.iconCriticalPrimary
Type.Info -> ElementTheme.colors.textBadgeInfo
Type.Info -> ElementTheme.colors.iconInfoPrimary
}
Badge(
text = data.text,

View file

@ -23,9 +23,9 @@ import androidx.compose.ui.graphics.LinearGradientShader
import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.components.drawWithLayer
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.utils.drawWithLayer
/**
* Gradient background for FTUE (onboarding) screens.

View file

@ -0,0 +1,44 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.designsystem.colors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.graphics.Color
import io.element.android.compound.theme.ElementTheme
@Composable
@ReadOnlyComposable
fun gradientActionColors(): List<Color> = listOf(
ElementTheme.colors.gradientActionStop1,
ElementTheme.colors.gradientActionStop2,
ElementTheme.colors.gradientActionStop3,
ElementTheme.colors.gradientActionStop4,
)
@Composable
@ReadOnlyComposable
fun gradientSubtleColors(): List<Color> = listOf(
ElementTheme.colors.gradientSubtleStop1,
ElementTheme.colors.gradientSubtleStop2,
ElementTheme.colors.gradientSubtleStop3,
ElementTheme.colors.gradientSubtleStop4,
ElementTheme.colors.gradientSubtleStop5,
ElementTheme.colors.gradientSubtleStop6,
)
@Composable
@ReadOnlyComposable
fun gradientInfoColors(): List<Color> = listOf(
ElementTheme.colors.gradientInfoStop1,
ElementTheme.colors.gradientInfoStop2,
ElementTheme.colors.gradientInfoStop3,
ElementTheme.colors.gradientInfoStop4,
ElementTheme.colors.gradientInfoStop5,
ElementTheme.colors.gradientInfoStop6,
)

View file

@ -1,578 +0,0 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.designsystem.components
import android.graphics.Bitmap
import android.graphics.Typeface
import android.os.Build
import android.text.TextPaint
import androidx.annotation.FloatRange
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.IconButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.center
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.ClipOp
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.LinearGradientShader
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.RadialGradientShader
import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.clipPath
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFontFamilyResolver
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isSpecified
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
import coil3.SingletonImageLoader
import coil3.request.ImageRequest
import coil3.request.allowHardware
import coil3.toBitmap
import com.airbnb.android.showkase.annotation.ShowkaseComposable
import com.vanniktech.blurhash.BlurHash
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.colors.AvatarColorsProvider
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.toDp
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.MediumTopAppBar
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.CommonDrawables
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.math.max
import kotlin.math.roundToInt
/**
* Default bloom configuration values.
*/
object BloomDefaults {
/**
* Number of components to use with BlurHash to generate the blur effect.
* Larger values mean more detailed blurs.
*/
const val HASH_COMPONENTS = 4
const val ENCODE_SIZE_PX = 20
const val DECODE_SIZE_PX = 5
/** Default bloom layers. */
@Composable
fun defaultLayers() = persistentListOf(
// Bottom layer
if (ElementTheme.isLightTheme) {
BloomLayer(0.2f, BlendMode.Hardlight)
} else {
BloomLayer(0.5f, BlendMode.Exclusion)
},
// Top layer
BloomLayer(if (ElementTheme.isLightTheme) 0.8f else 0.2f, BlendMode.Color),
)
}
/**
* Bloom layer configuration.
* @param alpha The alpha value to apply to the layer.
* @param blendMode The blend mode to apply to the layer.
*/
data class BloomLayer(
val alpha: Float,
val blendMode: BlendMode,
)
/**
* Bloom effect modifier. Applies a bloom effect to the component.
* @param hash The BlurHash to use as the bloom source.
* @param background The background color to use for the bloom effect. Since we use blend modes it must be non-transparent.
* @param blurSize The size of the bloom effect. If not specified the bloom effect will be the size of the component.
* @param offset The offset to use for the bloom effect. If not specified the bloom effect will be centered on the component.
* @param clipToSize The size to use for clipping the bloom effect. If not specified the bloom effect will not be clipped.
* @param layerConfiguration The configuration for the bloom layers. If not specified the default layers configuration will be used.
* @param bottomSoftEdgeColor The color to use for the bottom soft edge. If not specified the [background] color will be used.
* @param bottomSoftEdgeHeight The height of the bottom soft edge. If not specified the bottom soft edge will not be drawn.
* @param bottomSoftEdgeAlpha The alpha value to apply to the bottom soft edge.
* @param alpha The alpha value to apply to the bloom effect.
*/
@SuppressWarnings("ModifierComposed")
fun Modifier.bloom(
hash: String?,
background: Color,
blurSize: DpSize = DpSize.Unspecified,
offset: DpOffset = DpOffset.Unspecified,
clipToSize: DpSize = DpSize.Unspecified,
layerConfiguration: ImmutableList<BloomLayer>? = null,
bottomSoftEdgeColor: Color = background,
bottomSoftEdgeHeight: Dp = 40.dp,
@FloatRange(from = 0.0, to = 1.0)
bottomSoftEdgeAlpha: Float = 1.0f,
@FloatRange(from = 0.0, to = 1.0)
alpha: Float = 1f,
) = composed {
val defaultLayers = BloomDefaults.defaultLayers()
val layers = layerConfiguration ?: defaultLayers
// Bloom only works on API 29+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return@composed this
if (hash == null) return@composed this
val hashedBitmap = remember(hash) {
BlurHash.decode(
blurHash = hash,
width = BloomDefaults.DECODE_SIZE_PX,
height = BloomDefaults.DECODE_SIZE_PX,
)?.asImageBitmap()
} ?: return@composed this
val density = LocalDensity.current
val pixelSize = remember(blurSize, density) { blurSize.toIntSize(density) }
val clipToPixelSize = remember(clipToSize, density) { clipToSize.toIntSize(density) }
val bottomSoftEdgeHeightPixels = remember(bottomSoftEdgeHeight, density) { with(density) { bottomSoftEdgeHeight.roundToPx() } }
val isRTL = LocalLayoutDirection.current == LayoutDirection.Rtl
drawWithCache {
val dstSize = if (pixelSize != IntSize.Zero) {
pixelSize
} else {
IntSize(size.width.toInt(), size.height.toInt())
}
// Calculate where to place the center of the bloom effect
val centerOffset = if (offset.isSpecified) {
if (isRTL) {
IntOffset(
size.width.roundToInt() - offset.x.roundToPx(),
size.height.roundToInt() - offset.y.roundToPx(),
)
} else {
IntOffset(
offset.x.roundToPx(),
offset.y.roundToPx(),
)
}
} else {
IntOffset(
size.center.x.toInt(),
size.center.y.toInt(),
)
}
// Calculate the offset to draw the different layers and apply clipping
// This offset is applied to place the top left corner of the bloom effect
val layersOffset = if (offset.isSpecified) {
// Offsets the layers so the center of the bloom effect is at the provided offset value
IntOffset(
centerOffset.x - dstSize.width / 2,
centerOffset.y - dstSize.height / 2,
)
} else {
// Places the layers at the center of the component
IntOffset.Zero
}
val radius = max(dstSize.width, dstSize.height).toFloat() / 2
val circularGradientShader = RadialGradientShader(
centerOffset.toOffset(),
radius,
listOf(Color.Red, Color.Transparent),
listOf(0f, 1f)
)
val circularGradientBrush = ShaderBrush(circularGradientShader)
val bottomEdgeGradient = LinearGradientShader(
from = IntOffset(0, clipToPixelSize.height - bottomSoftEdgeHeightPixels).toOffset(),
to = IntOffset(0, clipToPixelSize.height).toOffset(),
listOf(Color.Transparent, bottomSoftEdgeColor),
listOf(0f, 1f)
)
val bottomEdgeGradientBrush = ShaderBrush(bottomEdgeGradient)
onDrawBehind {
if (dstSize != IntSize.Zero) {
val circleClipPath = Path().apply {
addOval(Rect(centerOffset.toOffset(), radius - 1))
}
// Clip the external radius of bloom gradient too, otherwise we have a 1px border
clipPath(circleClipPath, clipOp = ClipOp.Intersect) {
// Draw the bloom layers
drawWithLayer {
// Clip rect to the provided size if needed
if (clipToPixelSize != IntSize.Zero) {
drawContext.canvas.clipRect(Rect(Offset.Zero, clipToPixelSize.toSize()), ClipOp.Intersect)
}
// Draw background color for blending
drawRect(background, size = pixelSize.toSize())
// Draw layers
for (layer in layers) {
drawImage(
hashedBitmap,
srcSize = IntSize(BloomDefaults.HASH_COMPONENTS, BloomDefaults.HASH_COMPONENTS),
dstSize = dstSize,
dstOffset = layersOffset,
alpha = layer.alpha * alpha,
blendMode = layer.blendMode,
)
}
// Mask the layers erasing the outer radius using the gradient brush
drawCircle(
circularGradientBrush,
radius,
centerOffset.toOffset(),
blendMode = BlendMode.DstIn
)
}
}
// Draw the bottom soft edge
drawRect(
bottomEdgeGradientBrush,
topLeft = IntOffset(0, clipToPixelSize.height - bottomSoftEdgeHeight.roundToPx()).toOffset(),
size = IntSize(pixelSize.width, bottomSoftEdgeHeight.roundToPx()).toSize(),
alpha = bottomSoftEdgeAlpha
)
}
}
}
}
/**
* Bloom effect modifier for avatars. Applies a bloom effect to the component.
* @param avatarData The avatar data to use as the bloom source.
* If the avatar data has a URL it will be used as the bloom source, otherwise the initials will be used.
* @param background The background color to use for the bloom effect. Since we use blend modes it must be non-transparent.
* @param blurSize The size of the bloom effect. If not specified the bloom effect will be the size of the component.
* @param offset The offset to use for the bloom effect. If not specified the bloom effect will be centered on the component.
* @param clipToSize The size to use for clipping the bloom effect. If not specified the bloom effect will not be clipped.
* @param bottomSoftEdgeColor The color to use for the bottom soft edge. If not specified the [background] color will be used.
* @param bottomSoftEdgeHeight The height of the bottom soft edge. If not specified the bottom soft edge will not be drawn.
* @param bottomSoftEdgeAlpha The alpha value to apply to the bottom soft edge.
* @param alpha The alpha value to apply to the bloom effect.
*/
@SuppressWarnings("ModifierComposed")
fun Modifier.avatarBloom(
avatarData: AvatarData,
background: Color,
blurSize: DpSize = DpSize.Unspecified,
offset: DpOffset = DpOffset.Unspecified,
clipToSize: DpSize = DpSize.Unspecified,
bottomSoftEdgeColor: Color = background,
bottomSoftEdgeHeight: Dp = 40.dp,
@FloatRange(from = 0.0, to = 1.0)
bottomSoftEdgeAlpha: Float = 1.0f,
@FloatRange(from = 0.0, to = 1.0)
alpha: Float = 1f,
) = composed {
// Bloom only works on API 29+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return@composed this
// Request the avatar contents to use as the bloom source
val context = LocalContext.current
if (avatarData.url != null) {
val painterRequest = remember(avatarData) {
ImageRequest.Builder(context)
.data(avatarData)
// Allow cache and default dispatchers
.defaults(ImageRequest.Defaults())
// Needed to be able to read pixels from the Bitmap for the hash
.allowHardware(false)
// Reduce size so it loads faster for large avatars
.size(BloomDefaults.ENCODE_SIZE_PX, BloomDefaults.ENCODE_SIZE_PX)
.build()
}
// By making it saveable, we'll 'cache' the previous bloom effect until a new one is loaded
var blurHash by rememberSaveable(avatarData) { mutableStateOf<String?>(null) }
LaunchedEffect(avatarData) {
withContext(Dispatchers.IO) {
val bitmap = SingletonImageLoader.get(context)
.execute(painterRequest)
.image
?.toBitmap()
?: return@withContext
blurHash = BlurHash.encode(
bitmap = bitmap,
componentX = BloomDefaults.HASH_COMPONENTS,
componentY = BloomDefaults.HASH_COMPONENTS,
)
}
}
bloom(
hash = blurHash,
background = background,
blurSize = blurSize,
offset = offset,
clipToSize = clipToSize,
bottomSoftEdgeColor = bottomSoftEdgeColor,
bottomSoftEdgeHeight = bottomSoftEdgeHeight,
bottomSoftEdgeAlpha = bottomSoftEdgeAlpha,
alpha = alpha,
)
} else {
// There is no URL so we'll generate an avatar with the initials and use that as the bloom source
val avatarColors = AvatarColorsProvider.provide(avatarData.id)
val initialsBitmap = initialsBitmap(
width = BloomDefaults.ENCODE_SIZE_PX.toDp(),
height = BloomDefaults.ENCODE_SIZE_PX.toDp(),
text = avatarData.initialLetter,
textColor = avatarColors.foreground,
backgroundColor = avatarColors.background,
)
val hash = remember(avatarData, avatarColors) {
BlurHash.encode(
bitmap = initialsBitmap.asAndroidBitmap(),
componentX = BloomDefaults.HASH_COMPONENTS,
componentY = BloomDefaults.HASH_COMPONENTS,
)
}
bloom(
hash = hash,
background = background,
blurSize = blurSize,
offset = offset,
clipToSize = clipToSize,
bottomSoftEdgeColor = bottomSoftEdgeColor,
bottomSoftEdgeHeight = bottomSoftEdgeHeight,
bottomSoftEdgeAlpha = bottomSoftEdgeAlpha,
alpha = alpha,
)
}
}
// Used to create a Bitmap version of the initials avatar
@Composable
private fun initialsBitmap(
text: String,
backgroundColor: Color,
textColor: Color,
width: Dp = 32.dp,
height: Dp = 32.dp,
): ImageBitmap = with(LocalDensity.current) {
val backgroundPaint = remember(backgroundColor) {
Paint().also { it.color = backgroundColor }
}
val resolver: FontFamily.Resolver = LocalFontFamilyResolver.current
val fontSize = remember { height.toSp() / 2 }
val typeface: Typeface = remember(resolver) {
resolver.resolve(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Normal,
)
}.value as Typeface
val textPaint = remember(textColor, typeface) {
TextPaint().apply {
color = textColor.toArgb()
textSize = fontSize.toPx()
this.typeface = typeface
}
}
val textMeasurer = rememberTextMeasurer()
val result = remember(text) { textMeasurer.measure(text, TextStyle.Default.copy(fontSize = fontSize)) }
val centerPx = remember(width, height) { IntOffset(width.roundToPx() / 2, height.roundToPx() / 2) }
remember(text, width, height, backgroundColor, textColor) {
val bitmap = Bitmap.createBitmap(width.roundToPx(), height.roundToPx(), Bitmap.Config.ARGB_8888).asImageBitmap()
androidx.compose.ui.graphics.Canvas(bitmap).also { canvas ->
canvas.drawCircle(centerPx.toOffset(), width.toPx() / 2, backgroundPaint)
canvas.nativeCanvas.drawText(text, centerPx.x.toFloat() - result.size.width / 2, centerPx.y * 2f - result.size.height / 2 - 4, textPaint)
}
bitmap
}
}
// Translates DP sizes into pixel sizes, taking into account unspecified values
private fun DpSize.toIntSize(density: Density) = with(density) {
if (isSpecified) {
IntSize(width.roundToPx(), height.roundToPx())
} else {
IntSize.Zero
}
}
/**
* Helper to draw to a canvas using layers. This allows us to apply clipping to those layers only.
*/
fun DrawScope.drawWithLayer(block: DrawScope.() -> Unit) {
with(drawContext.canvas.nativeCanvas) {
val checkPoint = saveLayer(null, null)
block()
restoreToCount(checkPoint)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@PreviewsDayNight
@ShowkaseComposable(group = PreviewGroup.Bloom)
@Composable
internal fun BloomPreview() {
val blurhash = "eePn{tI?xExEja}ooKWWodjtNJoKR,j@a|sBWpS3WDbGazoKWWWWj@"
var topAppBarHeight by remember { mutableIntStateOf(-1) }
val topAppBarState = rememberTopAppBarState()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(topAppBarState)
ElementPreview(
drawableFallbackForImages = CommonDrawables.sample_avatar,
) {
Scaffold(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
Box {
MediumTopAppBar(
modifier = Modifier
.onSizeChanged { size ->
topAppBarHeight = size.height
}
.bloom(
hash = blurhash,
background = ElementTheme.colors.bgCanvasDefault,
blurSize = DpSize(430.dp, 430.dp),
offset = DpOffset(24.dp, 24.dp),
clipToSize = if (topAppBarHeight > 0) DpSize(430.dp, topAppBarHeight.toDp()) else DpSize.Zero,
),
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = Color.Black.copy(alpha = 0.05f),
),
navigationIcon = {
Avatar(
avatarData = AvatarData(
id = "sample-avatar",
name = "sample",
url = "aURL",
size = AvatarSize.CurrentUserTopBar,
),
avatarType = AvatarType.User,
)
},
actions = {
IconButton(onClick = {}) {
Icon(
imageVector = CompoundIcons.ShareAndroid(),
contentDescription = null,
)
}
},
title = {
Text("Title")
},
scrollBehavior = scrollBehavior,
)
}
},
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.consumeWindowInsets(paddingValues)
.fillMaxSize()
.verticalScroll(rememberScrollState()),
) {
repeat(20) {
Text("Content", modifier = Modifier.padding(vertical = 20.dp))
}
}
}
}
}
class InitialsColorIntProvider : PreviewParameterProvider<Int> {
override val values: Sequence<Int>
get() = sequenceOf(0, 1, 2, 3, 4, 5, 6, 7)
}
@PreviewsDayNight
@Composable
@ShowkaseComposable(group = PreviewGroup.Bloom)
internal fun BloomInitialsPreview(@PreviewParameter(InitialsColorIntProvider::class) color: Int) {
ElementPreview {
val avatarColors = AvatarColorsProvider.provide("$color")
val bitmap = initialsBitmap(text = "F", backgroundColor = avatarColors.background, textColor = avatarColors.foreground)
val hash = BlurHash.encode(
bitmap = bitmap.asAndroidBitmap(),
componentX = BloomDefaults.HASH_COMPONENTS,
componentY = BloomDefaults.HASH_COMPONENTS,
)
Box(
modifier = Modifier
.size(256.dp)
.bloom(
hash = hash,
background = if (ElementTheme.isLightTheme) {
// Workaround to display a very subtle bloom for avatars with very soft colors
Color(0xFFF9F9F9)
} else {
ElementTheme.colors.bgCanvasDefault
},
bottomSoftEdgeColor = ElementTheme.colors.bgCanvasDefault,
blurSize = DpSize(256.dp, 256.dp),
),
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier
.size(32.dp)
.clip(CircleShape),
painter = BitmapPainter(bitmap),
contentDescription = null
)
}
}
}

View file

@ -36,10 +36,8 @@ import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import io.element.android.compound.annotations.CoreColorToken
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.internal.DarkColorTokens
import io.element.android.compound.tokens.generated.internal.LightColorTokens
import io.element.android.libraries.designsystem.colors.gradientActionColors
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
@ -47,7 +45,6 @@ import io.element.android.libraries.designsystem.theme.components.ButtonSize
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.lowHorizontalPaddingValue
@OptIn(CoreColorToken::class)
@Composable
fun SuperButton(
onClick: () -> Unit,
@ -66,34 +63,21 @@ fun SuperButton(
ButtonSize.Small -> PaddingValues(horizontal = 16.dp, vertical = 5.dp)
}
}
val isLightTheme = ElementTheme.isLightTheme
val colors = if (LocalBuildMeta.current.isEnterpriseBuild) {
listOf(
ElementTheme.colors.textActionAccent,
ElementTheme.colors.textActionAccent,
)
} else {
remember(isLightTheme) {
if (isLightTheme) {
listOf(
LightColorTokens.colorBlue900,
LightColorTokens.colorGreen1100,
)
} else {
listOf(
DarkColorTokens.colorBlue900,
DarkColorTokens.colorGreen1100,
)
}
}
gradientActionColors()
}
val shaderBrush = remember(colors) {
object : ShaderBrush() {
override fun createShader(size: Size): Shader {
return LinearGradientShader(
from = Offset(0f, size.height),
to = Offset(size.width, 0f),
from = Offset(0f, 0f),
to = Offset(0f, size.height),
colors = colors,
)
}

View file

@ -0,0 +1,98 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.designsystem.modifiers
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.colors.gradientSubtleColors
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
/**
* Ref: https://www.figma.com/design/kcnHxunG1LDWXsJhaNuiHz/ER-145--Workspaces-V1?node-id=1141-24692
*/
@Stable
@Composable
fun Modifier.backgroundVerticalGradient(
isVisible: Boolean = true,
isEnterpriseBuild: Boolean = LocalBuildMeta.current.isEnterpriseBuild,
): Modifier {
if (!isVisible) return this
return background(
brush = Brush.verticalGradient(
colorStops = subtleColorStops(isEnterpriseBuild),
),
alpha = 0.75f,
)
}
@Composable
fun subtleColorStops(
isEnterpriseBuild: Boolean = LocalBuildMeta.current.isEnterpriseBuild,
): Array<Pair<Float, Color>> {
return buildList {
if (isEnterpriseBuild) {
// For enterprise builds, ensure that we are theming the gradient
add(0f to ElementTheme.colors.textActionAccent.copy(alpha = 0.5f))
add(0.75f to ElementTheme.colors.bgCanvasDefault)
add(1f to Color.Transparent)
} else {
val colors = gradientSubtleColors()
colors.forEachIndexed { index, color ->
add(index.toFloat() / (colors.size - 1) to color)
}
}
}.toTypedArray()
}
@PreviewsDayNight
@Composable
internal fun BackgroundVerticalGradientPreview() = ElementPreview {
Box(
modifier = Modifier
.fillMaxWidth()
.height(height = 100.dp)
.backgroundVerticalGradient()
)
}
@PreviewsDayNight
@Composable
internal fun BackgroundVerticalGradientEnterprisePreview() = ElementPreview {
Box(
modifier = Modifier
.fillMaxWidth()
.height(height = 100.dp)
.backgroundVerticalGradient(
isEnterpriseBuild = true,
)
)
}
@PreviewsDayNight
@Composable
internal fun BackgroundVerticalGradientDisabledPreview() = ElementPreview {
Box(
modifier = Modifier
.fillMaxWidth()
.height(height = 100.dp)
.backgroundVerticalGradient(
isVisible = false,
)
)
}

View file

@ -11,7 +11,6 @@ package io.element.android.libraries.designsystem.preview
object PreviewGroup {
const val AppBars = "App Bars"
const val Avatars = "Avatars"
const val Bloom = "Bloom"
const val BottomSheets = "Bottom Sheets"
const val Buttons = "Buttons"
const val DateTimePickers = "DateTime pickers"

View file

@ -52,11 +52,6 @@ val SemanticColors.messageFromOtherBackground
val SemanticColors.progressIndicatorTrackColor
get() = if (isLight) LightColorTokens.colorAlphaGray500 else DarkColorTokens.colorAlphaGray500
// This color is not present in Semantic color, so put hard-coded value for now
@OptIn(CoreColorToken::class)
val SemanticColors.iconSuccessPrimaryBackground
get() = if (isLight) LightColorTokens.colorGreen300 else DarkColorTokens.colorGreen300
// This color is not present in Semantic color, so put hard-coded value for now
@OptIn(CoreColorToken::class)
val SemanticColors.bgSubtleTertiary
@ -71,57 +66,10 @@ val SemanticColors.temporaryColorBgSpecial
val SemanticColors.pinDigitBg
get() = if (isLight) LightColorTokens.colorGray300 else DarkColorTokens.colorGray400
@OptIn(CoreColorToken::class)
val SemanticColors.currentUserMentionPillText
get() = if (isLight) LightColorTokens.colorGreen1100 else DarkColorTokens.colorGreen1100
val SemanticColors.currentUserMentionPillBackground
get() = if (isLight) {
// We want LightDesignTokens.colorGreenAlpha400
Color(0x3b07b661)
} else {
// We want DarkDesignTokens.colorGreenAlpha500
Color(0xff003d29)
}
val SemanticColors.mentionPillText
get() = textPrimary
val SemanticColors.mentionPillBackground
get() = if (isLight) {
// We want LightDesignTokens.colorGray400
Color(0x1f052e61)
} else {
// We want DarkDesignTokens.colorGray500
Color(0x26f4f7fa)
}
@OptIn(CoreColorToken::class)
val SemanticColors.bigCheckmarkBorderColor
get() = if (isLight) LightColorTokens.colorGray400 else DarkColorTokens.colorGray400
@OptIn(CoreColorToken::class)
val SemanticColors.highlightedMessageBackgroundColor
get() = if (isLight) LightColorTokens.colorGreen300 else DarkColorTokens.colorGreen300
// Badge colors
@OptIn(CoreColorToken::class)
val SemanticColors.badgeNeutralBackgroundColor
get() = if (isLight) LightColorTokens.colorAlphaGray300 else DarkColorTokens.colorAlphaGray300
@OptIn(CoreColorToken::class)
val SemanticColors.badgeNeutralContentColor
get() = if (isLight) LightColorTokens.colorGray1100 else DarkColorTokens.colorGray1100
@OptIn(CoreColorToken::class)
val SemanticColors.badgeNegativeBackgroundColor
get() = if (isLight) LightColorTokens.colorAlphaRed300 else DarkColorTokens.colorAlphaRed300
@OptIn(CoreColorToken::class)
val SemanticColors.badgeNegativeContentColor
get() = if (isLight) LightColorTokens.colorRed1100 else DarkColorTokens.colorRed1100
@OptIn(CoreColorToken::class)
val SemanticColors.pinnedMessageBannerIndicator
get() = if (isLight) LightColorTokens.colorAlphaGray600 else DarkColorTokens.colorAlphaGray600
@ -146,9 +94,7 @@ internal fun ColorAliasesPreview() = ElementPreview {
"messageFromOtherBackground" to ElementTheme.colors.messageFromOtherBackground,
"progressIndicatorTrackColor" to ElementTheme.colors.progressIndicatorTrackColor,
"temporaryColorBgSpecial" to ElementTheme.colors.temporaryColorBgSpecial,
"iconSuccessPrimaryBackground" to ElementTheme.colors.iconSuccessPrimaryBackground,
"bigCheckmarkBorderColor" to ElementTheme.colors.bigCheckmarkBorderColor,
"highlightedMessageBackgroundColor" to ElementTheme.colors.highlightedMessageBackgroundColor,
)
)
}

View file

@ -0,0 +1,22 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.designsystem.utils
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.nativeCanvas
/**
* Helper to draw to a canvas using layers. This allows us to apply clipping to those layers only.
*/
fun DrawScope.drawWithLayer(block: DrawScope.() -> Unit) {
with(drawContext.canvas.nativeCanvas) {
val checkPoint = saveLayer(null, null)
block()
restoreToCount(checkPoint)
}
}

View file

@ -30,7 +30,6 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.iconSuccessPrimaryBackground
@Composable
internal fun FormattingOption(
@ -42,13 +41,13 @@ internal fun FormattingOption(
modifier: Modifier = Modifier,
) {
val backgroundColor = when (state) {
FormattingOptionState.Selected -> ElementTheme.colors.iconSuccessPrimaryBackground
FormattingOptionState.Selected -> ElementTheme.colors.bgAccentSelected
FormattingOptionState.Default,
FormattingOptionState.Disabled -> Color.Transparent
}
val foregroundColor = when (state) {
FormattingOptionState.Selected -> ElementTheme.colors.iconSuccessPrimary
FormattingOptionState.Selected -> ElementTheme.colors.iconAccentPrimary
FormattingOptionState.Default -> ElementTheme.colors.iconSecondary
FormattingOptionState.Disabled -> ElementTheme.colors.iconDisabled
}

View file

@ -18,12 +18,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.LinearGradientShader
import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.colors.gradientActionColors
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
@ -62,13 +62,7 @@ internal fun SendButton(
modifier = Modifier
.clip(CircleShape)
.size(36.dp)
.then(
if (canSendMessage) {
buttonBackgroundModifier()
} else {
Modifier
}
)
.buttonBackgroundModifier(canSendMessage)
) {
Icon(
modifier = Modifier
@ -91,27 +85,30 @@ internal fun SendButton(
}
}
private fun buttonBackgroundModifier() = Modifier.drawWithCache {
// We have a square button, so height == width.
val height = size.height
val verticalGradientBrush = ShaderBrush(
LinearGradientShader(
from = Offset(0f, 0f),
to = Offset(0f, height),
colors = listOf(
Color(0xFF79DD98),
Color(0xFF0DBD8B),
Color(0xFF128585),
Color(0xFF24446B),
@Composable
private fun Modifier.buttonBackgroundModifier(
canSendMessage: Boolean,
) = then(
if (canSendMessage) {
val colors = gradientActionColors()
Modifier.drawWithCache {
val verticalGradientBrush = ShaderBrush(
LinearGradientShader(
from = Offset(0f, 0f),
to = Offset(0f, size.height),
colors = colors,
)
)
)
)
onDrawBehind {
drawRect(
brush = verticalGradientBrush,
)
onDrawBehind {
drawRect(
brush = verticalGradientBrush,
)
}
}
} else {
Modifier
}
}
)
@PreviewsDayNight
@Composable

View file

@ -28,10 +28,6 @@ import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.rememberTypeface
import io.element.android.libraries.designsystem.theme.currentUserMentionPillBackground
import io.element.android.libraries.designsystem.theme.currentUserMentionPillText
import io.element.android.libraries.designsystem.theme.mentionPillBackground
import io.element.android.libraries.designsystem.theme.mentionPillText
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.MatrixClient
@ -69,10 +65,10 @@ class MentionSpanTheme(val currentUserId: UserId) {
@Suppress("ComposableNaming")
@Composable
fun updateStyles() {
currentUserTextColor = ElementTheme.colors.currentUserMentionPillText.toArgb()
currentUserBackgroundColor = ElementTheme.colors.currentUserMentionPillBackground.toArgb()
otherTextColor = ElementTheme.colors.mentionPillText.toArgb()
otherBackgroundColor = ElementTheme.colors.mentionPillBackground.toArgb()
currentUserTextColor = ElementTheme.colors.textBadgeAccent.toArgb()
currentUserBackgroundColor = ElementTheme.colors.bgBadgeAccent.toArgb()
otherTextColor = ElementTheme.colors.textPrimary.toArgb()
otherBackgroundColor = ElementTheme.colors.bgBadgeDefault.toArgb()
typeface.value = ElementTheme.typography.fontBodyLgMedium.rememberTypeface().value
val density = LocalDensity.current

View file

@ -8,8 +8,10 @@
package io.element.android.tests.konsist
import androidx.compose.ui.tooling.preview.PreviewLightDark
import com.google.common.truth.Truth.assertThat
import com.lemonappdev.konsist.api.Konsist
import com.lemonappdev.konsist.api.ext.list.withAllAnnotationsOf
import com.lemonappdev.konsist.api.ext.list.withName
import com.lemonappdev.konsist.api.ext.list.withoutName
import com.lemonappdev.konsist.api.verify.assertEmpty
import com.lemonappdev.konsist.api.verify.assertTrue
@ -52,95 +54,115 @@ class KonsistPreviewTest {
}
}
private val previewNameExceptions = listOf(
"AsyncIndicatorFailurePreview",
"AsyncIndicatorLoadingPreview",
"BackgroundVerticalGradientDisabledPreview",
"BackgroundVerticalGradientEnterprisePreview",
"BackgroundVerticalGradientPreview",
"ColorAliasesPreview",
"DefaultRoomListTopBarWithIndicatorPreview",
"FocusedEventEnterprisePreview",
"FocusedEventPreview",
"GradientFloatingActionButtonCircleShapePreview",
"HeaderFooterPageScrollablePreview",
"IconsCompoundPreview",
"IconsOtherPreview",
"MarkdownTextComposerEditPreview",
"MatrixBadgeAtomInfoPreview",
"MatrixBadgeAtomNegativePreview",
"MatrixBadgeAtomNeutralPreview",
"MatrixBadgeAtomPositivePreview",
"MessageComposerViewVoicePreview",
"MessagesReactionButtonAddPreview",
"MessagesReactionButtonExtraPreview",
"MessagesViewWithIdentityChangePreview",
"PageTitleWithIconFullPreview",
"PageTitleWithIconMinimalPreview",
"PendingMemberRowWithLongNamePreview",
"PinUnlockViewInAppPreview",
"PollAnswerViewDisclosedNotSelectedPreview",
"PollAnswerViewDisclosedSelectedPreview",
"PollAnswerViewEndedSelectedPreview",
"PollAnswerViewEndedWinnerNotSelectedPreview",
"PollAnswerViewEndedWinnerSelectedPreview",
"PollAnswerViewUndisclosedNotSelectedPreview",
"PollAnswerViewUndisclosedSelectedPreview",
"PollContentViewCreatorEditablePreview",
"PollContentViewCreatorEndedPreview",
"PollContentViewCreatorPreview",
"PollContentViewDisclosedPreview",
"PollContentViewEndedPreview",
"PollContentViewUndisclosedPreview",
"ReadReceiptBottomSheetPreview",
"RoomMemberListViewBannedPreview",
"SasEmojisPreview",
"SecureBackupSetupViewChangePreview",
"SelectedUserCannotRemovePreview",
"TextComposerAddCaptionPreview",
"TextComposerCaptionPreview",
"TextComposerEditCaptionPreview",
"TextComposerEditNotEncryptedPreview",
"TextComposerEditPreview",
"TextComposerFormattingNotEncryptedPreview",
"TextComposerFormattingPreview",
"TextComposerLinkDialogCreateLinkPreview",
"TextComposerLinkDialogCreateLinkWithoutTextPreview",
"TextComposerLinkDialogEditLinkPreview",
"TextComposerReplyPreview",
"TextComposerSimpleNotEncryptedPreview",
"TextComposerSimplePreview",
"TextComposerVoiceNotEncryptedPreview",
"TextComposerVoicePreview",
"TextFieldDialogWithErrorPreview",
"TimelineImageWithCaptionRowPreview",
"TimelineItemEventRowForDirectRoomPreview",
"TimelineItemEventRowShieldPreview",
"TimelineItemEventRowTimestampPreview",
"TimelineItemEventRowUtdPreview",
"TimelineItemEventRowWithManyReactionsPreview",
"TimelineItemEventRowWithRRPreview",
"TimelineItemEventRowWithReplyPreview",
"TimelineItemGroupedEventsRowContentCollapsePreview",
"TimelineItemGroupedEventsRowContentExpandedPreview",
"TimelineItemImageViewHideMediaContentPreview",
"TimelineItemVideoViewHideMediaContentPreview",
"TimelineItemVoiceViewUnifiedPreview",
"TimelineVideoWithCaptionRowPreview",
"TimelineViewMessageShieldPreview",
"UserAvatarColorsPreview",
"UserProfileHeaderSectionWithVerificationViolationPreview",
"VoiceItemViewPlayPreview",
)
@Test
fun `previewNameExceptions is sorted alphabetically`() {
assertThat(previewNameExceptions.sorted()).isEqualTo(previewNameExceptions)
}
@Test
fun `previewNameExceptions only contains existing functions`() {
val names = previewNameExceptions.toMutableSet()
Konsist
.scopeFromProject()
.functions()
.withAllAnnotationsOf(PreviewsDayNight::class)
.withName(previewNameExceptions)
.let {
it.forEach { function ->
names.remove(function.name)
}
}
assertThat(names).isEmpty()
}
@Test
fun `Functions with '@PreviewsDayNight' have correct name`() {
Konsist
.scopeFromProject()
.functions()
.withAllAnnotationsOf(PreviewsDayNight::class)
.withoutName(
"AsyncIndicatorFailurePreview",
"AsyncIndicatorLoadingPreview",
"BloomInitialsPreview",
"BloomPreview",
"CallScreenPipViewPreview",
"ColorAliasesPreview",
"DefaultRoomListTopBarWithIndicatorPreview",
"FocusedEventPreview",
"GradientFloatingActionButtonCircleShapePreview",
"HeaderFooterPageScrollablePreview",
"IconsCompoundPreview",
"IconsOtherPreview",
"MarkdownTextComposerEditPreview",
"MatrixBadgeAtomPositivePreview",
"MatrixBadgeAtomNeutralPreview",
"MatrixBadgeAtomNegativePreview",
"MatrixBadgeAtomInfoPreview",
"MentionSpanPreview",
"MessageComposerViewVoicePreview",
"MessagesReactionButtonAddPreview",
"MessagesReactionButtonExtraPreview",
"MessagesViewWithIdentityChangePreview",
"MessagesViewWithTypingPreview",
"PageTitleWithIconFullPreview",
"PageTitleWithIconMinimalPreview",
"PendingMemberRowWithLongNamePreview",
"PinUnlockViewInAppPreview",
"PollAnswerViewDisclosedNotSelectedPreview",
"PollAnswerViewDisclosedSelectedPreview",
"PollAnswerViewEndedSelectedPreview",
"PollAnswerViewEndedWinnerNotSelectedPreview",
"PollAnswerViewEndedWinnerSelectedPreview",
"PollAnswerViewUndisclosedNotSelectedPreview",
"PollAnswerViewUndisclosedSelectedPreview",
"PollContentViewCreatorEditablePreview",
"PollContentViewCreatorEndedPreview",
"PollContentViewCreatorPreview",
"PollContentViewDisclosedPreview",
"PollContentViewEndedPreview",
"PollContentViewUndisclosedPreview",
"ReadReceiptBottomSheetPreview",
"RoomMemberListViewBannedPreview",
"SasEmojisPreview",
"SecureBackupSetupViewChangePreview",
"SelectedUserCannotRemovePreview",
"TextComposerAddCaptionPreview",
"TextComposerCaptionPreview",
"TextComposerEditPreview",
"TextComposerEditNotEncryptedPreview",
"TextComposerEditCaptionPreview",
"TextComposerFormattingPreview",
"TextComposerFormattingNotEncryptedPreview",
"TextComposerLinkDialogCreateLinkPreview",
"TextComposerLinkDialogCreateLinkWithoutTextPreview",
"TextComposerLinkDialogEditLinkPreview",
"TextComposerReplyPreview",
"TextComposerReplyNotEncryptedPreview",
"TextComposerSimplePreview",
"TextComposerSimpleNotEncryptedPreview",
"TextComposerVoicePreview",
"TextComposerVoiceNotEncryptedPreview",
"TextFieldDialogWithBorderPreview",
"TextFieldDialogWithErrorPreview",
"TimelineImageWithCaptionRowPreview",
"TimelineItemEventRowForDirectRoomPreview",
"TimelineItemEventRowShieldPreview",
"TimelineItemEventRowTimestampPreview",
"TimelineItemEventRowUtdPreview",
"TimelineItemEventRowWithManyReactionsPreview",
"TimelineItemEventRowWithRRPreview",
"TimelineItemEventRowWithReplyPreview",
"TimelineItemGroupedEventsRowContentCollapsePreview",
"TimelineItemGroupedEventsRowContentExpandedPreview",
"TimelineItemImageViewHideMediaContentPreview",
"TimelineItemVideoViewHideMediaContentPreview",
"TimelineItemVoiceViewUnifiedPreview",
"TimelineVideoWithCaptionRowPreview",
"TimelineViewMessageShieldPreview",
"UserAvatarColorsPreview",
"UserProfileHeaderSectionWithVerificationViolationPreview",
"VoiceItemViewPlayPreview",
)
.withoutName(previewNameExceptions)
.assertTrue(
additionalMessage = "Functions for Preview should be named like this: <ViewUnderPreview>Preview. " +
"Exception can be added to the test, for multiple Previews of the same view",

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:62a7e346397466a3a98bdd2e3dfcad43d65471017d40aa719c00ac2032ff9509
size 41231
oid sha256:f2adacc3ef3537cc0b81377786d03084f4923a7da1401ab84d03c907569e52d4
size 27010

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:abe3d11ea9f0088529da6651526650a1bd2fe34335da31f043cfc8b1fb945597
size 49428
oid sha256:fb861a1c8c4188b1586384b65c78ed18ca23030066e21c0245606077f89f3c29
size 24909

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d079c9142b483482b12e2e55253bb02cf9ff1f37b95d455b1887d54dbfbbb9b0
size 40941
oid sha256:4a449498d150466a5b3e27001228a39e935be7c637c422955375a18ea6f1fcb2
size 26754

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:09c215a680812b95b07501aba343331932c53e8988d13b43e8f3296d84664f63
size 49089
oid sha256:c4ee431749b5f051b7345ac7f141f88ee8f998f04a2b7743960c5fa27af638e7
size 24605

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a7b0dc90002ec59917ed0c64dae785088117c5aca61961c3d71dc7c05d2fa72b
size 82238
oid sha256:347d73d129f6b49b502371bc0f090e7432bf28f95aade88e37f33b6e91ac44a7
size 67519

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:660e96d8c627d8f24bbfbc150a3e2b7e74f6819e7fbaa581c5872f544ed9685e
size 46034
oid sha256:2cba6e6115836c2274ad829b149d5c85973baf560f8b11e3b7d30023282b8ca8
size 34967

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f65131302fab3b0ba710528947376b57230a74731aa6549473998c30f4e07f6e
size 40681
oid sha256:8a6fa99ca8f18b0fef17abe4d9c8de4a9f34f8d0ae097f55bb6e860f4f7098fb
size 29681

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:20e6d5a1c7218304ede990c1906ec628593acbe57a6496b98a2c1fe9c87d1ae3
size 105294
oid sha256:83807fc3cc614bfc1abb99e062df4dce72aa92626ffb5a24a3fcb357cc5e95c1
size 90869

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6041a7693307a30183606e7f8f2b605ba4c9bfb8c7ad5d8122837749813d6e2c
size 99928
oid sha256:6d5f42641d0566d91fbb55ea5f05d54979eb66c6e0b12c01d84aec1c90c218b8
size 85395

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:765619612593a9821c31afb6268259890d9374099f5e26ae9897aca09abf06e9
size 82801
oid sha256:1eca7e08ad504da3750c622b73450511030a5d0856d7ecede0d23402d39d8855
size 68807

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a7b0dc90002ec59917ed0c64dae785088117c5aca61961c3d71dc7c05d2fa72b
size 82238
oid sha256:347d73d129f6b49b502371bc0f090e7432bf28f95aade88e37f33b6e91ac44a7
size 67519

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:51d89e7c0e8c205c230351c4a00e0d560f3f54c6b423ae3b2b801fd6d00343bf
size 83509
oid sha256:cc132c097ec62633e72292861654fa825f1962eacf8cdcea9553aa0475e2e2fd
size 68790

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e7383bea26cbd09cff981e6b4202f7e9bca788094ea482241b45eaefcbf16127
size 34927
oid sha256:6766c513026e13417d0371f196d69b712a900feeb1f814cda14ba34df188c73a
size 23921

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a7b0dc90002ec59917ed0c64dae785088117c5aca61961c3d71dc7c05d2fa72b
size 82238
oid sha256:347d73d129f6b49b502371bc0f090e7432bf28f95aade88e37f33b6e91ac44a7
size 67519

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4385b380efbae924d4a8fece278a61003d40acbd795fef1aa2eab4c5fadbf367
size 60691
oid sha256:a195f301e7a3b41c3b485cf1e448cde6d12312d0ad06cc48ea5c3cfab2621040
size 53715

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7d64fa49cfca399eb073a8efac881aebbfed38726116d3113f9a562245ac9a41
size 60495
oid sha256:0d26274f91876c8a79846131149b38f544171cdef0396dfaa07c8c3e482aeb61
size 53530

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:020d732b7b3a4f43918bee7b9424ffd03fe20074c5823341811a1c700551b2f2
size 58734
oid sha256:63cb36a2139a260f5589d334dfe012414fa4404d297499d04fb630d206db3b9a
size 51750

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:63f8198b96e49f9a29dad22ef2249503d1528aec874599d9daab3f8634d662d7
size 99704
oid sha256:f67ce8fdbd81b01cee6f1b8f2f1d3f735f14b9afd5c218b3890ee53129b04f39
size 85193

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ca1e0f43d447faf770ee7aa4f443f3a310f520c9437cbd5baca9ac7ce36d2aae
size 88613
oid sha256:3d67192da73b958451d78fdfadbe1d3150fdeed8effd391d98122d4275aac348
size 63310

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6be30df0d8f5ba0a57c720bcc16eefc3a5b7dacfc8115a8d47a42edab3e63e82
size 53250
oid sha256:8ef7257132254bc1a6092da64c643efe1e4ac9f75566f81b05b2afc768248364
size 30605

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1c6dc3c0c72d1ea8a980f393d7cfe2c9e0392016c8fc25b901817dd077d8526c
size 47265
oid sha256:37d62040e49a4ae81ca161b377dab60491984ae1784ed79123658ff4a2df6e18
size 24939

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4c93289bc30c3b590360f916a55a86a5cc98028bd2201cb410f329de85ed615c
size 111059
oid sha256:8600a1be8fd98cf8776e6887b51f5948a28d3f90ade2a189a52a1c1f90655afe
size 85983

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7563629f9df4012d7cc35b2805e1ac381ccdb171935b35cad0d1ea60186e3e7d
size 105496
oid sha256:af5dd69eda38ecc5b461e0f11d2ee1556e8f85b5d26ea28dc90bef41d97f6c9d
size 80498

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d7f9c804e0f2b75c84a99081d33d12c46ef2aed62aaf044ec1f957f728f586b1
size 88564
oid sha256:53552b4c4ebcdfee0da6a64138f7f07080f2d2a0e7db195b327466854b9484b8
size 64494

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ca1e0f43d447faf770ee7aa4f443f3a310f520c9437cbd5baca9ac7ce36d2aae
size 88613
oid sha256:3d67192da73b958451d78fdfadbe1d3150fdeed8effd391d98122d4275aac348
size 63310

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3962f724784add8dc33c7327dd866cab76eff0d53abb781b94e974ae68212e0b
size 90485
oid sha256:3dde30b95371e46ad6d36dfd7c95e2a1e6968517d75b11d16512d5ac53337bb0
size 65102

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4850c3002637166016ac75129c0fd4081c540848969b40eb84c4f60a4366c111
size 43857
oid sha256:6d6454d10e26c7c1ffb14931c38af49ce025a5c0853930c1cddf1f7b2b5e1967
size 21562

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ca1e0f43d447faf770ee7aa4f443f3a310f520c9437cbd5baca9ac7ce36d2aae
size 88613
oid sha256:3d67192da73b958451d78fdfadbe1d3150fdeed8effd391d98122d4275aac348
size 63310

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:acde7691ec0ebe690bcb29478963107e2dc0fe6fa54892b3600f50e0cf1db2a3
size 69463
oid sha256:44c89fe4f267ae04e0ed8f8076e97a90d4a1c55aea47349a16abe8985925677e
size 51064

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:38e55635243d77c084053c4feea03dcedabd549aa556f4a622ec2d629bb4f426
size 69240
oid sha256:146044e82709dfd8262082b31789eed72e9bebc100a7a8f15a7b8d41f5a95ad1
size 50833

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1fc45bd4279456b75cadcb8381451f9e6a0e1fadaf4a8f22274c94aa89c7c348
size 67496
oid sha256:b516e9ad28282895de6671c152aa57e4d8e74345b3745d8b909b39d81e29ebb7
size 49075

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8d9f42109a12e450a13d1166f7d7790913e1dd847cd44f00a2c881d825cad9b4
size 105496
oid sha256:93e35351228d382dd6c890441f09536915b1cd10f2a6592a6f90f52783dc3e46
size 80482

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9163c7aaae605af5470506d99eed7ecd19374fa7e719f8b0318e55f1319702a2
size 43406
oid sha256:c7944e3a428379cd240ef04f81ce82b67f1f42cc4c35c1e986ee37af35b8fcf8
size 38434

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:634df4b9fab9797663e11293e5894de9854e918184d227e73e47be047bcb9623
size 50335
oid sha256:c6ac5f05e753028ee09aaaaf6564113199aff3dada0b42ec534d826ef0aaa96d
size 45309

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a971216d0cfc58f65b3589479420c02f2a04010ac27d79ea2434075dcd6b4f7c
size 51283
oid sha256:3feefaf49170429ce228284a10fc27e3396965a89913ae075fe48ca752029f8d
size 46343

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8dcb5fb864d2692e62de1161e3420a3c9e9d7a338110b5e3b7791753e7995b7a
size 45863
oid sha256:9b47188101c77d3422756c3a904e2c2a567a18cc6b551b77d98cdb465133357d
size 40870

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2ee424d3f53556823f1bc19a1bf0d742918dff295962f4dda371459c8607fc15
size 34990
oid sha256:ae8c3144d1b7e5eb19bf708a48ee4d112d8f95cb9113a2f724e36c63a94bd458
size 30060

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6f19a41fa98afa02f2d913c02cfd8cca6e53d6af164ab1c81d98cc75ae877bbc
size 36305
oid sha256:d3de02fbfc362822fc901339e1c17f832be36dc68a51521e6ceda95b78ab3c05
size 31380

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0e527cea5e937b1ab89fcbc0388ab5d81008ceef8e85b25af227f4c8465df268
size 31504
oid sha256:c863e3132eb572e402067d1c9076b8d91925ce44e50a01975275201f9d1dea5c
size 26625

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2604ac285ee6334a46fe348313fe2abe655860bd9b54f237fc92d0f99e38a7be
size 29162
oid sha256:706915d5e1b6867b044400aa5c11a2da9b69db257343e3fb0c307eb673e60bcd
size 27743

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e01e9453b8c72e9a1a55bbb4f14e88836f93d1d9b28af10557aec81eae847d74
size 43374
oid sha256:22324bf8af409876b3bc6706332761ad66301418f51e05169884b9a6aeda0d2d
size 38252

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:90b00f22d1d2d1b22f6260c95ea3fe39851682d258df7012b89c68db2af01601
size 50133
oid sha256:c1703ac3e99ffa2fd8ea915399bc508a5fc2df66917ae0898986419d731f968a
size 44984

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:18d2e25e32b0dc63b72a2c8c32213d2c7d9d499bf568b88addf24709e433ce34
size 50997
oid sha256:3faabcb1ca9d3cc47f446203ca4037fdf5af2072992c55cd8b20a989393a1990
size 45894

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7bed222380fcd708584b52aa098622487025598c870ac3ed68694dcf7e900038
size 45755
oid sha256:d16cf478ee1c7873706ee1056685b9b7feb0b1a8fdb5dbd31a272bc8536b671e
size 40624

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:445c343404fba0ed533ffe07a138ddf74686f67e3e163626963be4e5db17fd7f
size 34479
oid sha256:4350a822cb6444db068314bd4add32cef9798ff11bd42c3f4e018f11cbe84a1c
size 29371

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2e2489ed1cbbd10709456b0a910b8bbb6607732bbf40c4ea30114306875fe999
size 36282
oid sha256:4d05fb7926b5e09e638a2777e97d51069c993320575a6df41a389a0a362d5c9d
size 31210

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5b7b89d817b0d1847828b214a1340fc041e85a44d43fc89dae64e6c56324ac41
size 31806
oid sha256:ae27e9cae53436ce7b24c2650fafd0c521de8f69efadba4470e4a0474824e2a5
size 26793

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1ce6e3a17d5b092b4f292659cea45834207b4956e273b7cecc69c7a07daa8d51
size 27255
oid sha256:3f2800add49143a4ff174f1d873ba89d9e54376d6474248f49a31cedfbd691fe
size 26139

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb50ba951a464a7bae750e779f25e2deeda0355a3220fdaf19f60c7c7ed73649
size 395064
oid sha256:724df787c6c033c40967bfc62de74e71613ef8221bfc18a9859fcc0b5fb9a8ca
size 394854

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:503e64a8acb9630e3a6cece0028283407bd54fda2f8ca7bd41c09d12491b48d1
size 394757
oid sha256:a4b9d7cfa35310566401bc031a2554e49ea71820b3050abc449f91fad3ad892a
size 394573

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0ce509e6357edda6b8206eea46a7818386423c96abaddae8ae8e886244a93784
size 51168
oid sha256:3ccf51958e5757064087f6a589032b7bb25b30827ecfc7864f157a6da689a67c
size 51024

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb50ba951a464a7bae750e779f25e2deeda0355a3220fdaf19f60c7c7ed73649
size 395064
oid sha256:724df787c6c033c40967bfc62de74e71613ef8221bfc18a9859fcc0b5fb9a8ca
size 394854

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:089a84e094a191836ae654f3214b1b3a590a1b13e0d1972c8ef2ac82813b47eb
size 51139
oid sha256:15782147de5dc2fda9d77f75694bba22d9998db5c245969baa192e87543387cf
size 50991

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:21a0d4fabf74730d3aff1ec23502c18d84cb53899d8cacfefede48ae8fde19e1
size 89266
oid sha256:9476e888ecf6ac7d374d146a8aa5bfa3ed66b0c4b722d46052b56d177a543733
size 89112

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7af7dedec8de4ac367fc870d2c235060b3a57d27b2bb879dc784a3cef3c2a370
size 390628
oid sha256:36aa7ce68da79c229612156d1870c904a87482b64bc9eb262d2c52c1e813d31d
size 390438

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb50ba951a464a7bae750e779f25e2deeda0355a3220fdaf19f60c7c7ed73649
size 395064
oid sha256:724df787c6c033c40967bfc62de74e71613ef8221bfc18a9859fcc0b5fb9a8ca
size 394854

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:19066a9752af1dbd27a3403dbc0a85d3b7dfa6b794cfcc314934a91c3e680283
size 54925
oid sha256:0420404d363d85a0a1215ac8d11f474e148294d6b4818acb65ab6196536c61f9
size 55243

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8ead97d45b6ff18a36c589af87a07a3f7e415a1308b7b5cd327de76631063f71
size 63435
oid sha256:9fced59a8634cae33f42a73cfa1eb2f89ffbec5317622eaf0b1f777ac7eb271b
size 63693

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ed176e71df15c6da072badc6db227580ab5b4a4ef41932754fd21c9816c6b16b
size 63857
oid sha256:e31c7c892a67cf0f36ad2ba20cc87771d59e8165bdfb1bd3fb0adfc53dd79509
size 64042

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:70e6b6c940f935d585e210ffa10fa2926f074e4d1ee255e8607345379d2a52f0
size 55326
oid sha256:2ba3d44f3ab519ae5e2b85a6b93f14c7c38e8ea8cc52baba03e9efef1bcb1145
size 54197

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b37969501cdec9c6cbd3604058c420fc95ca239743dcf8f6e88903d3c76c563b
size 67268
oid sha256:38f24750122c90c71d78000c749a6fae6d440bd3c6b3031cae638c5826f627e9
size 66323

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f61cc2b5b7f836d898ef9fa850230ada79303d4caa0f56005a0cb78bc526c184
size 66888
oid sha256:136f087952cc1f7ef024dfcc84668107e76c2adf8984715ef3c42ebeea68686f
size 65837

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:53cd6b98e2f553049f5146e9c73598cd12e7b2199f414473b4258dd37714ce30
size 9376

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f978eab0f072af32d574493c0cd89bf8b77749371a64b2dedc0a4f8f4cc29c42
size 9069

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a2ea87ef947697fc3ea21c8953149db1c729be5391a4fd65f63b7b04aaf0e8cf
size 10465
oid sha256:2cd45bad53eed3d47521819d8f8ea750c2bcd7eeccc263a262d7f25093809207
size 10658

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8ce07ba72b4542584273ee4004365fe50da4747e4aa06c46ea0d6b192647a1ac
size 10204
oid sha256:b2c4de97ea960abc79d73ba7ed7eb41202581ab779ad3ce506b9467269bf7e35
size 8477

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bf32ecc91206e24bca38616066b94d6d451f69b1d274dff63e30b73f9b0b5fe2
size 16822
oid sha256:2e72c1cecdd62de867d300206d4d524f51d8445ed345364d999ec117bea2e0a3
size 16926

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0d791d1c3a79e409a43fc5fa3aa368ead2c8aa7167f86e50d477aca6341482f6
size 16877
oid sha256:4856a029136e5dcc86f21ac66a615d6fd991cbce182e25015ad355b8e0e94fef
size 15915

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bf3f23066d6466f799677dd139d92ff26aeccdc7ce20d1ea4a1fa2eddbc28ecc
size 50157
oid sha256:baea5001524ac109a9b74ca933b28b596706263aef7a5702f0c014343ad98ce6
size 50370

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a78bdd930bc853c4adde525d48f8477e3731cb3e76fc6802ada6350f73e209ce
size 331247
oid sha256:1f6b9808cd09f5be5c2468bbed0a51a49933b9dd6151b6326b1845e8e4a046c1
size 331804

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6eeeacf0a4282d7fd5bc8ef3b67680d72d254c51a8ebd009c9c197180a9a6f0b
size 85092
oid sha256:5836fb3172f8628993031751a8cebc840167d157776cee7821ac22c84a0db558
size 85427

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c242b43c5674dfbe72ad5399ec2a1094fb5d9c8d2ff5d627e49c6f51010fbdf2
size 51631
oid sha256:a5fed03c9b5212a5b654ae872af03171923c8944ac9fb69ed233e608603439ed
size 51806

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9aef1a43f5478f4a8e2715211695d0dffbfdd823d8642244c4411eef06794bee
size 63393
oid sha256:4bb8a5c67177e2f246e07947d242d0395049409992a108f6cde27fa88ac5d2d9
size 63465

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:117ffd9be0ee1c2ce742c1725f935662ddbbf060bf02a3d121f3b331f577eb03
size 48007
oid sha256:ca73884d2ce866368e6f4a99de05730bac5994e93e8bfcb933d23784407b801a
size 48178

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:986daeac63a5185195c9972fb6d6d64cccc1eec53855c755b88d8e4c364acceb
size 64484
oid sha256:fcde38afea018df257c1ee6f6d17ea969b225ff2acd12f5ed9bcb0414134bcd1
size 64571

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4b9bfd23b94519ee37e26e5a6c99e7ec2ac1513cae6cfa8465c71856d1e8afce
size 55197
oid sha256:cbf93914e100be8c32f20c32067d0b3660f728a46ba8e43e54cbbb3173ead4f0
size 55352

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:17fa1c2967b460d47e33dbbbc1f10085941d2861a619c9c4e09d0ad62a6a0e5c
size 64217
oid sha256:16937747975115844bcaf9fdb0d94ba04059389704066973664b9f3b0baa0587
size 64449

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:30ea27e644a6ad86506cebaef9ba6d3558b30945ed57e80ddcbf6cd22896d408
size 71126
oid sha256:e58cfe72872014d51678373b5500b41201728304c301e6ebd855b2241185e453
size 71228

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a8fc7a5830027eb95476df6e378269c1ff37d57c75aac50e3353647d4e84249a
size 492060
oid sha256:dd648155ac39ce346b8ebd24657bafcaf3668020798b3d50e814e54addc28bdf
size 493522

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:949ea4bca530c513ff4e67f4b3b67a69215931ab1fe9bc42b02ed347f02a9bf6
size 487167
oid sha256:cf409a30502ffe99c991fc5315b34a1f2cb9ef27ffaaa230512752bd80b32d2f
size 488703

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8479ff3816b6d08e1b61f9326f525f55f40b6a542baedbfa5f5acbb5c766150e
size 70264
oid sha256:3bd60ebe776788f2c31f6b3e4e9b2ba9e814a559b78554175648b31e04c19247
size 70305

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4d9a77ed75ee8797ff6e76b19d8e873fdcb55b2c361094c875874403887803c1
size 85027
oid sha256:ea3d7407d2b6f544606c72d2e6dc2b93e3bac1b05a5676b7527dc1a8dff37f7d
size 85260

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4767b56c7df7009caa31e905cfa473edfce505b9171214987a0c2be8f76f0558
size 73338
oid sha256:8877cec4c29fab0b4dff35ab963907c6ba05e222581ecd6b81375c6f13ca55b5
size 73416

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bccefb7d0464ec26c54333804962362923da1cffb834a31b35ccf0460a6420ef
size 103533
oid sha256:54f9c6207d148648bb231e3956faeacb2f6a29af9532a4a8fe74e4db91875a52
size 103802

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:039d1a97453e7c74cc8e2c04ecad719b3dffa1cc023b611e8f6e909fc9d51090
size 53106
oid sha256:18c3c814f0decaac5608be87344c0fe03509ade2072fca88d774e3858424ff38
size 53443

Some files were not shown because too many files have changed in this diff Show more