Add workaround for blur in ElementLogoAtom for OS < 9. (#1061)
* Add workaround for blur in `ElementLogoAtom` for OS < 9. * Update screenshots * Pass `useBlurredShadow` to `ElementLogoAtom` * Update screenshots --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
8a62abe93e
commit
75c19a7e04
16 changed files with 178 additions and 84 deletions
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package io.element.android.libraries.designsystem.atomic.atoms
|
||||
|
||||
import android.graphics.BlurMaskFilter
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
|
|
@ -27,24 +26,16 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.clip
|
||||
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.draw.shadow
|
||||
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.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.ElementPreview
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
|
|
@ -53,6 +44,7 @@ import io.element.android.libraries.theme.ElementTheme
|
|||
fun ElementLogoAtom(
|
||||
size: ElementLogoAtomSize,
|
||||
modifier: Modifier = Modifier,
|
||||
useBlurredShadow: Boolean = canUseBlurMaskFilter(),
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
) {
|
||||
val blur = if (darkTheme) 160.dp else 24.dp
|
||||
|
|
@ -66,22 +58,35 @@ fun ElementLogoAtom(
|
|||
.border(size.borderWidth, borderColor, RoundedCornerShape(size.cornerRadius)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.size(size.outerSize)
|
||||
.shapeShadow(
|
||||
color = shadowColor,
|
||||
cornerRadius = size.cornerRadius,
|
||||
blurRadius = size.shadowRadius,
|
||||
offsetY = 8.dp,
|
||||
)
|
||||
)
|
||||
if (useBlurredShadow) {
|
||||
Box(
|
||||
Modifier
|
||||
.size(size.outerSize)
|
||||
.blurredShapeShadow(
|
||||
color = shadowColor,
|
||||
cornerRadius = size.cornerRadius,
|
||||
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(
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(size.cornerRadius))
|
||||
.size(size.outerSize)
|
||||
.background(backgroundColor)
|
||||
.blur(blur)
|
||||
.blurCompat(blur)
|
||||
)
|
||||
Image(
|
||||
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
|
||||
@DayNightPreviews
|
||||
internal fun ElementLogoAtomMediumPreview() {
|
||||
|
|
@ -172,7 +139,19 @@ internal fun ElementLogoAtomLargePreview() {
|
|||
}
|
||||
|
||||
@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 {
|
||||
Box(
|
||||
Modifier
|
||||
|
|
@ -180,7 +159,7 @@ private fun ContentToPreview(elementLogoAtomSize: ElementLogoAtomSize) {
|
|||
.background(ElementTheme.colors.bgSubtlePrimary),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
ElementLogoAtom(elementLogoAtomSize)
|
||||
ElementLogoAtom(elementLogoAtomSize, useBlurredShadow = useBlurredShadow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue