Skip to content

Commit

Permalink
Demo app: slow renders animation (#627)
Browse files Browse the repository at this point in the history
* added some basic animation

* created an animation of yellow silly comets to create a meteor shower effect after adding the comet book to cart. Added a few ways to make the animation slow render on purpose

* comments

* idk

* cleanup

* cleanup

* readme update
  • Loading branch information
magda-woj authored Oct 15, 2024
1 parent 1b01ca0 commit 75dbce5
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 8 deletions.
2 changes: 1 addition & 1 deletion demo-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ The OpenTelemetry Android Demo App currently supports the following features:
- Automatically detects instances of slow rendering within the app.
- Slow render events are captured as spans, providing information on when and where rendering delays occurred.
- The span includes attributes such as `activity.name`, `screen.name`, `count`, and network details to help diagnose performance issues.
- Note: The app currently does not have any features designed to intentionally trigger slow rendering.
- To trigger a slowly rendering animation in the demo app, add any quantity of The Comet Book (the last product on the product list) to the cart. Note that the number of `slow-render` spans and their respective `count` attributes may vary between runs or across different machines.

* Manual Instrumentation
- Provides access to the OpenTelemetry APIs for manual instrumentation, allowing developers to create custom spans and events as needed.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package io.opentelemetry.android.demo.shop.ui.components

import androidx.compose.animation.core.*
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.zIndex
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.security.SecureRandom

@Composable
fun SlowCometAnimation(modifier: Modifier = Modifier) {
val cometParams = listOf(
CometParams(startX = 0f, startY = 0f, targetX = 1.3f, targetY = 1.4f),
CometParams(startX = 0.2f, startY = 0f, targetX = 1.4f, targetY = 0.9f),
CometParams(startX = 0.2f, startY = -0.1f, targetX = 1.5f, targetY = 0.8f),
CometParams(startX = 0f, startY = 0.6f, targetX = 1.2f, targetY = 1.7f),
CometParams(startX = -0.2f, startY = 0.1f, targetX = 1.4f, targetY = 1.3f),
CometParams(startX = 0.5f, startY = 0f, targetX = 1.7f, targetY = 1.3f),
CometParams(startX = 0f, startY = 0f, targetX = 1.3f, targetY = 1.4f),
CometParams(startX = 0.2f, startY = 0f, targetX = 1.4f, targetY = 1.3f),
CometParams(startX = 0f, startY = 0f, targetX = 1.4f, targetY = 1.3f),
CometParams(startX = 0.2f, startY = -0.1f, targetX = 1.5f, targetY = 0.7f),
CometParams(startX = 0f, startY = 0.6f, targetX = 1.2f, targetY = 1.7f),
CometParams(startX = -0.2f, startY = 0.1f, targetX = 1.3f, targetY = 1.4f),
)

val cometVisibility = remember { mutableStateListOf<Boolean>().apply { addAll(List(cometParams.size) { false }) } }

LaunchedEffect(Unit) {
cometParams.forEachIndexed { index, _ ->
launch {
delay(index * 500L)
cometVisibility[index] = true
}
}
}

cometParams.forEachIndexed { index, params ->
if (cometVisibility[index]) {
SlowSingleCometAnimation(
startX = params.startX,
startY = params.startY,
targetX = params.targetX,
targetY = params.targetY,
modifier = modifier
)
}
}
}

@Composable
fun SlowSingleCometAnimation(
startX: Float,
startY: Float,
targetX: Float,
targetY: Float,
modifier: Modifier = Modifier
) {
val scope = rememberCoroutineScope()

val tailPositions = remember { mutableStateListOf<Offset>() }

val animatedX = remember { Animatable(startX) }
val animatedY = remember { Animatable(startY) }

LaunchedEffect(Unit) {
scope.launch {
launch {
delay(500)
animatedX.animateTo(
targetValue = targetX,
animationSpec = tween(durationMillis = 3000, easing = LinearEasing)
)
}
launch {
delay(500)
animatedY.animateTo(
targetValue = targetY,
animationSpec = tween(durationMillis = 3000, easing = LinearEasing)
)
}
}
}

LaunchedEffect(animatedX.value, animatedY.value) {
val currentPosition = Offset(animatedX.value, animatedY.value)
tailPositions.add(currentPosition)

if (tailPositions.size > 30) {
tailPositions.removeAt(0)
}
}

val random = SecureRandom()
repeat(10_000) {
random.nextFloat()
}

ExpensiveCometShape(
modifier = modifier,
headX = animatedX.value,
headY = animatedY.value,
tailPositions = tailPositions
)
}

@Composable
fun ExpensiveCometShape(
modifier: Modifier = Modifier,
headX: Float = 0.8f,
headY: Float = 0.3f,
tailPositions: List<Offset> = emptyList()
) {
Canvas(modifier = modifier) {
val headRadius = size.minDimension * 0.05f
val animatedHeadX = size.width * headX
val animatedHeadY = size.height * headY


for (i in tailPositions.indices) {
val position = tailPositions[i]
val posX = size.width * position.x
val posY = size.height * position.y

val progress = i / tailPositions.size.toFloat()
val tailRadius = headRadius * (1 - progress)
val tailAlpha = 0.3f * (1 - progress)

repeat(100) {
drawCircle(
color = Color(0xFFFFD700).copy(alpha = tailAlpha),
radius = tailRadius,
center = Offset(posX, posY)
)
}
}

repeat(100) {
drawCircle(
color = Color(0x80FFFF00),
radius = headRadius * 2,
center = Offset(animatedHeadX, animatedHeadY)
)
drawCircle(
color = Color(0xAAFFA500),
radius = headRadius * 1.5f,
center = Offset(animatedHeadX, animatedHeadY)
)
drawCircle(
color = Color(0xFFFFD700),
radius = headRadius,
center = Offset(animatedHeadX, animatedHeadY)
)
drawCircle(
color = Color.White,
radius = headRadius * 0.6f,
center = Offset(animatedHeadX, animatedHeadY)
)
}
}
}


@Preview(showBackground = true)
@Composable
fun PreviewCometAnimation() {
SlowCometAnimation(
Modifier
.fillMaxSize()
.zIndex(1f)
)
}

data class CometParams(
val startX: Float,
val startY: Float,
val targetX: Float,
val targetY: Float
)
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import io.opentelemetry.android.demo.shop.ui.cart.CartViewModel
import io.opentelemetry.android.demo.shop.ui.components.UpPressButton
import androidx.compose.ui.Alignment
import androidx.compose.ui.zIndex
import io.opentelemetry.android.demo.shop.clients.ProductCatalogClient
import io.opentelemetry.android.demo.shop.clients.RecommendationService
import io.opentelemetry.android.demo.shop.ui.components.SlowCometAnimation
import io.opentelemetry.android.demo.shop.ui.components.ConfirmCrashPopup
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
Expand All @@ -39,6 +41,8 @@ fun ProductDetails(
val sourceProductImage = imageLoader.load(product.picture)
var quantity by remember { mutableIntStateOf(1) }

var slowRender by remember { mutableStateOf(false) }

val productsClient = ProductCatalogClient(context)
val recommendationService = remember { RecommendationService(productsClient, cartViewModel) }
val recommendedProducts = remember { recommendationService.getRecommendedProducts(product) }
Expand Down Expand Up @@ -86,7 +90,11 @@ fun ProductDetails(
Spacer(modifier = Modifier.height(32.dp))
QuantityChooser(quantity = quantity, onQuantityChange = { quantity = it })
Spacer(modifier = Modifier.height(16.dp))
AddToCartButton(cartViewModel = cartViewModel, product = product, quantity = quantity)
AddToCartButton(
cartViewModel = cartViewModel,
product = product,
quantity = quantity,
onSlowRenderChange = { slowRender = it })
Spacer(modifier = Modifier.height(32.dp))
RecommendedSection(recommendedProducts = recommendedProducts, onProductClick = onProductClick)
}
Expand All @@ -97,23 +105,33 @@ fun ProductDetails(
.align(Alignment.TopStart)
.padding(8.dp)
)
if (slowRender) {
SlowCometAnimation(
modifier = Modifier
.fillMaxSize()
.zIndex(1f)
)
}
}
}

@Composable
fun AddToCartButton(
cartViewModel: CartViewModel,
product: Product,
quantity: Int
quantity: Int,
onSlowRenderChange: (Boolean) -> Unit
) {

var showPopup by remember { mutableStateOf(false) }

Button(
onClick = {
if (product.id == "OLJCESPC7Z" && quantity == 10) {
showPopup = true
} else {
if (product.id == "HQTGWGPNH4") {
onSlowRenderChange(true)
}
cartViewModel.addProduct(product, quantity)
}
},
Expand Down Expand Up @@ -161,7 +179,3 @@ fun multiThreadCrashing(numThreads : Int = 4) {
}
latch.countDown()
}




0 comments on commit 75dbce5

Please sign in to comment.