Skip to content

Commit

Permalink
✨ EffectsTrait + Confetti
Browse files Browse the repository at this point in the history
  • Loading branch information
andretortolano committed Jul 18, 2024
1 parent b58346e commit 4ea5c40
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 8 deletions.
2 changes: 2 additions & 0 deletions appcues/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ dependencies {
// Play In-App Review
implementation 'com.google.android.play:review:2.0.1'
implementation 'com.google.android.play:review-ktx:2.0.1'
// Animation
implementation 'nl.dionsegijn:konfetti-compose:2.0.4'

testImplementation "junit:junit:4.13.2"
testImplementation "io.mockk:mockk:1.13.5"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ internal fun StyleColorResponse.mapComponentColor(): ComponentColor {
dark = dark,
)
}

internal fun List<String>?.mapToColors(): List<Long> {
return this?.map { normalizeToArgbLong(it) } ?: listOf()
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@ import com.appcues.data.remote.appcues.response.styling.StyleResponse
internal fun StyleResponse?.mapComponentStyle(): ComponentStyle {
if (this == null) return ComponentStyle()

fun StyleGradientColorResponse?.toComponentColorList(): List<ComponentColor>? {
if (this == null) return null
return arrayListOf<ComponentColor>().apply {
colors.forEach { fromColor ->
add(fromColor.mapComponentColor())
}
}
}
return ComponentStyle(
width = width,
height = height,
Expand All @@ -32,6 +24,7 @@ internal fun StyleResponse?.mapComponentStyle(): ComponentStyle {
backgroundColor = backgroundColor?.mapComponentColor(),
backgroundImage = backgroundImage?.mapComponentBackgroundImage(),
shadow = shadow?.mapComponentShadow(),
colors = colors.mapToColors(),
// Not dealing with direction, every gradient is horizontal from start to end
backgroundGradient = backgroundGradient.toComponentColorList(),
borderColor = borderColor?.mapComponentColor(),
Expand All @@ -45,3 +38,12 @@ internal fun StyleResponse?.mapComponentStyle(): ComponentStyle {
horizontalAlignment = mapComponentHorizontalAlignment(horizontalAlignment),
)
}

private fun StyleGradientColorResponse?.toComponentColorList(): List<ComponentColor>? {
if (this == null) return null
return arrayListOf<ComponentColor>().apply {
colors.forEach { fromColor ->
add(fromColor.mapComponentColor())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ internal fun AppcuesConfigMap.getConfigInt(key: String): Int? {
}
}

internal fun AppcuesConfigMap.getConfigDouble(key: String): Double? {
// if hash config is null, return default
if (this == null) return null
// get value by key as Double?
return get(key)?.let {
when (it) {
is Double -> it
is Int -> it.toDouble()
else -> null
}
}
}

internal fun AppcuesConfigMap.getConfigStyle(key: String): ComponentStyle? {
return getConfig<Any>(key)?.let {
MoshiConfiguration.fromAny<StyleResponse>(it).mapComponentStyle()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal data class ComponentStyle(
val borderColor: ComponentColor? = null,
val borderWidth: Double? = null,
val shadow: ComponentShadow? = null,
val colors: List<Long>? = null,

// Text related properties
val fontName: String? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ internal data class StyleResponse(
val paddingTrailing: Double? = null,
val cornerRadius: Double? = null,
val shadow: StyleShadowResponse? = null,
val colors: List<String>? = null,
val foregroundColor: StyleColorResponse? = null,
val backgroundColor: StyleColorResponse? = null,
val backgroundGradient: StyleGradientColorResponse? = null,
Expand Down
2 changes: 2 additions & 0 deletions appcues/src/main/java/com/appcues/trait/TraitRegistry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.appcues.trait.appcues.BackdropKeyholeTrait
import com.appcues.trait.appcues.BackdropTrait
import com.appcues.trait.appcues.BackgroundContentTrait
import com.appcues.trait.appcues.CarouselTrait
import com.appcues.trait.appcues.EffectsTrait
import com.appcues.trait.appcues.EmbedTrait
import com.appcues.trait.appcues.ModalTrait
import com.appcues.trait.appcues.PagingDotsTrait
Expand Down Expand Up @@ -47,6 +48,7 @@ internal class TraitRegistry(
register(TargetElementTrait.TYPE) { config, _ -> TargetElementTrait(config) }
register(TargetRectangleTrait.TYPE) { config, _ -> TargetRectangleTrait(config) }
register(BackdropTrait.TYPE) { config, _ -> BackdropTrait(config) }
register(EffectsTrait.TYPE) { config, _ -> EffectsTrait(config) }
register(BackdropKeyholeTrait.TYPE) { config, _ -> BackdropKeyholeTrait(config) }
register(CarouselTrait.TYPE) { config, _ -> CarouselTrait(config) }
register(PagingDotsTrait.TYPE) { config, _ -> PagingDotsTrait(config) }
Expand Down
54 changes: 54 additions & 0 deletions appcues/src/main/java/com/appcues/trait/appcues/EffectsTrait.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.appcues.trait.appcues

import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
import com.appcues.data.model.AppcuesConfigMap
import com.appcues.data.model.getConfig
import com.appcues.data.model.getConfigDouble
import com.appcues.data.model.getConfigInt
import com.appcues.data.model.getConfigStyle
import com.appcues.trait.AppcuesTraitException
import com.appcues.trait.BackdropDecoratingTrait
import com.appcues.trait.appcues.EffectsTrait.PresentationStyle.CONFETTI
import com.appcues.trait.appcues.effects.ConfettiEffect

internal class EffectsTrait(
override val config: AppcuesConfigMap,
) : BackdropDecoratingTrait {

companion object {

const val TYPE = "@appcues/effects"
const val DEFAULT_DURATION = 2000
const val DEFAULT_INTENSITY = 1.0
}

private enum class PresentationStyle {
CONFETTI
}

private val presentationStyle = config.getConfig<String?>("presentationStyle").toPresentationStyle()

private val duration = config.getConfigInt("duration") ?: DEFAULT_DURATION

private val intensity = config.getConfigDouble("intensity") ?: DEFAULT_INTENSITY

private val style = config.getConfigStyle("style")

@Composable
override fun BoxScope.BackdropDecorate(content: @Composable BoxScope.() -> Unit) {
// other backdrop decorate traits renders first (putting this one on top)
content()

when (presentationStyle) {
CONFETTI -> ConfettiEffect(style, duration, intensity)
}
}

private fun String?.toPresentationStyle(): PresentationStyle {
return when (this) {
"confetti" -> CONFETTI
else -> throw AppcuesTraitException("invalid effects presentation style: $this")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.appcues.trait.appcues.effects

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.appcues.data.mapper.styling.mapToColors
import com.appcues.data.model.styling.ComponentStyle
import nl.dionsegijn.konfetti.compose.KonfettiView
import nl.dionsegijn.konfetti.core.Party
import nl.dionsegijn.konfetti.core.Position.Relative
import nl.dionsegijn.konfetti.core.emitter.Emitter
import nl.dionsegijn.konfetti.core.models.Size
import java.util.concurrent.TimeUnit.MILLISECONDS

private const val EMITTER_INTENSITY_RAW = 200

@Composable
internal fun ConfettiEffect(style: ComponentStyle?, duration: Int, intensity: Double) {
// default colors in case colors style property is empty
val colors = remember {
val styleColors = style?.colors ?: listOf()

styleColors.ifEmpty { listOf("#5C5CFF", "#20E0D6", "#FF5290").mapToColors() }.map { it.toInt() }
}

// this is a center point on the top of the screen, that will disperse particles
// in a 180 angle spread before it start falling straight down
val party = Party(
speed = 0f,
maxSpeed = 60f,
damping = 0.9f,
spread = 180,
size = listOf(Size(sizeInDp = 10), Size(sizeInDp = 12), Size(sizeInDp = 16)),
angle = 270,
timeToLive = 5000,
fadeOutEnabled = true,
colors = colors,
position = Relative(x = 0.5, y = -0.05),
emitter = Emitter(duration = duration.toLong(), MILLISECONDS).perSecond((EMITTER_INTENSITY_RAW * intensity).toInt())
)

KonfettiView(
modifier = Modifier.fillMaxSize(),
parties = listOf(party),
)
}

0 comments on commit 4ea5c40

Please sign in to comment.