Skip to content

Commit

Permalink
Cleaned up code and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
NachoSoto committed Oct 18, 2023
1 parent ef31b22 commit 119c260
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,26 @@ import com.revenuecat.purchases.ui.revenuecatui.data.processed.TemplateConfigura
import com.revenuecat.purchases.ui.revenuecatui.extensions.conditional
import com.revenuecat.purchases.ui.revenuecatui.extensions.defaultBackgroundPlaceholder

// Current implementation uses a transformation on API level < 31, modifier on > 31.
@Composable
internal fun BoxScope.PaywallBackground(templateConfiguration: TemplateConfiguration) {
// current implementation uses a transformation on API level 30-, modifier on 31+.
val transformation = if (templateConfiguration.configuration.blurredBackgroundImage && Build.VERSION.SDK_INT < 31)
val supportsNativeBlurring = Build.VERSION.SDK_INT >= BackgroundUIConstants.minSDKVersionSupportingBlur
val shouldBlur = templateConfiguration.configuration.blurredBackgroundImage
val imageAlpha = if (shouldBlur) { BackgroundUIConstants.blurAlpha } else 1.0f

val backwardsCompatibleTransformation = if (shouldBlur && !supportsNativeBlurring) {
BlurTransformation(
context = LocalContext.current, radius = BackgroundUIConstants.blurSize.toFloatPx(),
scale = BackgroundUIConstants.blurScale
context = LocalContext.current,
radius = BackgroundUIConstants.blurSize.toFloatPx(),
scale = BackgroundUIConstants.blurScale,
)
else null
} else {
null
}

val modifier = Modifier
.matchParentSize()
// TODO: try to unify both methods into either a transformation or a modifier
// one notable difference is that the transformation works at the image level so it'd run only once
.conditional(
templateConfiguration.configuration.blurredBackgroundImage
&& Build.VERSION.SDK_INT >= 31
) {
.conditional(shouldBlur && supportsNativeBlurring) {
blur(BackgroundUIConstants.blurSize, edgeTreatment = BlurredEdgeTreatment.Unbounded)
}

Expand All @@ -47,25 +49,27 @@ internal fun BoxScope.PaywallBackground(templateConfiguration: TemplateConfigura
painter = painterResource(id = R.drawable.default_background),
contentDescription = null,
contentScale = BackgroundUIConstants.contentScale,
alpha = imageAlpha
)
} else {
templateConfiguration.images.backgroundUri?.let {
RemoteImage(
urlString = it.toString(),
modifier = modifier,
contentScale = BackgroundUIConstants.contentScale,
transformation = transformation,
alpha = BackgroundUIConstants.blurAlpha
transformation = backwardsCompatibleTransformation,
alpha = imageAlpha
)
}
}
}

private object BackgroundUIConstants {
val blurSize = 40.dp
const val blurAlpha = 0.7f
val contentScale = ContentScale.Crop
const val blurAlpha = 0.7f
const val blurScale = 0.5f
const val minSDKVersionSupportingBlur = 31
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.revenuecat.purchases.ui.revenuecatui.composables

import BlurTransformation
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
Expand All @@ -16,26 +15,22 @@ import coil.transform.Transformation
import com.revenuecat.purchases.ui.revenuecatui.UIConstant
import com.revenuecat.purchases.ui.revenuecatui.helpers.Logger

@SuppressWarnings("LongParameterList")
@Composable
internal fun RemoteImage(
urlString: String,
modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Fit,
contentDescription: String? = null,
transformation: Transformation? = null,
alpha: Float = 1f
alpha: Float = 1f,
) {

val requestBuilder = ImageRequest.Builder(LocalContext.current)
.data(urlString)
.crossfade(durationMillis = UIConstant.defaultAnimationDurationMillis)

transformation?.let {
requestBuilder.transformations(listOf(it))
}

return AsyncImage(
model = requestBuilder.build(),
model = ImageRequest.Builder(LocalContext.current)
.data(urlString)
.crossfade(durationMillis = UIConstant.defaultAnimationDurationMillis)
.transformations(listOfNotNull(transformation))
.build(),
contentDescription = contentDescription,
imageLoader = LocalContext.current.getRevenueCatUIImageLoader(),
modifier = modifier,
Expand All @@ -52,7 +47,6 @@ internal fun RemoteImage(
)
}


private const val MAX_CACHE_SIZE_BYTES = 25 * 1024 * 1024L // 25 MB

@Composable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import android.content.Context
import android.graphics.Bitmap
import android.os.Build
import android.renderscript.Allocation
import android.renderscript.Element
import android.renderscript.RenderScript
import android.renderscript.ScriptIntrinsicBlur
import coil.size.Size
import coil.transform.Transformation
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.math.abs
import kotlin.math.min
import kotlin.math.roundToInt

/**
* BlurTransformation class applies a blur on a given Bitmap image.
Expand All @@ -25,7 +20,7 @@ import kotlin.math.roundToInt
*/
internal class BlurTransformation(
private val context: Context,
private val radius: Float = 25f,
private val radius: Float,
private val scale: Float = 0.5f,
) : Transformation {

Expand All @@ -46,12 +41,14 @@ internal class BlurTransformation(
override fun hashCode(): Int = radius.hashCode()
}

// max radius supported by RenderScript
private const val MAX_SUPPORTED_RADIUS = 25

internal fun Bitmap.blur(context: Context, radius: Float = 25f): Bitmap {
if (radius < 1f) {
return this@blur
}
// max radius supported by RenderScript is 25
val updatedRadius = min(radius.toDouble(), 25.toDouble())
val updatedRadius = min(radius.toDouble(), MAX_SUPPORTED_RADIUS.toDouble())

val rs = RenderScript.create(context)
val input = Allocation.createFromBitmap(rs, this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,38 @@ import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import blur
import blurByAveraging
import blurUsingRenderScript
import kotlinx.coroutines.runBlocking
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class BlurBenchmarkTest {

class PaywallDataValidationTest {

@Test
fun `Blur images using native blur`() = runBlocking {
benchmarkBlur("Native Blur") { image, context, radius ->
image.blur(context = context, radius = radius)
}
@Ignore("Test is for checking performance only")
@Test
fun `Blur images using native blur`() = runBlocking {
benchmarkBlur("Native Blur") { image, context, radius ->
image.blur(context = context, radius = radius)
}
}

private suspend fun benchmarkBlur(functionName: String, blurFunction: suspend (Bitmap, Context, Float) -> Unit) {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val image = openImage()
val radius = 25f
val iterations = 100
val startTime = System.currentTimeMillis()
repeat(iterations) {
blurFunction(image, context, radius)
}
val endTime = System.currentTimeMillis()
val totalTime = endTime - startTime
val averageTime = totalTime / iterations
Log.d(TAG, "$functionName:\nTotal time: $totalTime ms, Average time: $averageTime ms")
private suspend fun benchmarkBlur(functionName: String, blurFunction: suspend (Bitmap, Context, Float) -> Unit) {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val image = openImage()
val radius = 25f
val iterations = 100
val startTime = System.currentTimeMillis()
repeat(iterations) {
blurFunction(image, context, radius)
}
val endTime = System.currentTimeMillis()
val totalTime = endTime - startTime
val averageTime = totalTime / iterations
Log.d(TAG, "$functionName:\nTotal time: $totalTime ms, Average time: $averageTime ms")
}

private fun openImage(): Bitmap {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val inputStream = context.assets.open("sample_blurring_image.jpeg")
return BitmapFactory.decodeStream(inputStream)
}
private fun openImage(): Bitmap {
val inputStream = javaClass.classLoader!!.getResource("sample_blurring_image.jpeg").openStream()
return BitmapFactory.decodeStream(inputStream)
}
}

0 comments on commit 119c260

Please sign in to comment.