Skip to content

Commit

Permalink
Add Starfield-based message board
Browse files Browse the repository at this point in the history
  • Loading branch information
mmartosdev committed Sep 30, 2023
1 parent a9a9718 commit 7fcbfd8
Show file tree
Hide file tree
Showing 2 changed files with 220 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package com.bumble.puzzyx.composable

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameMillis
import androidx.compose.ui.BiasAlignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import com.bumble.appyx.interactions.core.ui.math.smoothstep
import com.bumble.appyx.navigation.collections.ImmutableList
import com.bumble.appyx.navigation.collections.toImmutableList
import com.bumble.puzzyx.composable.StarField.Companion.generateStars
import com.bumble.puzzyx.model.Entry
import com.bumble.puzzyx.model.entries
import com.bumble.puzzyx.ui.appyx_dark
import kotlinx.coroutines.isActive
import kotlin.random.Random

@Immutable
private data class StarFieldSpecs(
val regularStarCounter: Int = 200,
val speed: Float = 0.1f,
val zNewCoord: Float = 0f,
val zFadeInStart: Float = 0.3f,
val zFadeInEnd: Float = 0.4f,
val zFadeOutStart: Float = 1.3f,
val zFadeOutEnd: Float = 1.4f,
)

@Immutable
private sealed class Star {
abstract val xCoord: Float
abstract val yCoord: Float
abstract val zCoord: Float

data class EntryStar(
override val xCoord: Float = Random.nextDouble(-1.0, 1.0).toFloat(),
override val yCoord: Float = Random.nextDouble(-1.0, 1.0).toFloat(),
override val zCoord: Float,
val entry: Entry,
) : Star()

data class RegularStar(
override val xCoord: Float = Random.nextDouble(-1.0, 1.0).toFloat(),
override val yCoord: Float = Random.nextDouble(-1.0, 1.0).toFloat(),
override val zCoord: Float,
val color: Color,
) : Star()
}

@Immutable
private data class StarField(
val specs: StarFieldSpecs,
val stars: ImmutableList<Star>
) {
companion object {
fun generateStars(starFieldSpecs: StarFieldSpecs): StarField =
StarField(
specs = starFieldSpecs,
stars = (entryStars(starFieldSpecs) + regularStars(starFieldSpecs))
.shuffled()
.toImmutableList()
)

private fun entryStars(starFieldSpecs: StarFieldSpecs) =
entries.map {
Star.EntryStar(
zCoord = Random.nextDouble(
from = starFieldSpecs.zNewCoord.toDouble(),
until = starFieldSpecs.zFadeOutEnd.toDouble(),
).toFloat(),
entry = it,
)
}

private fun regularStars(starFieldSpecs: StarFieldSpecs) =
Array(starFieldSpecs.regularStarCounter) {
Star.RegularStar(
zCoord = Random.nextDouble(
from = starFieldSpecs.zNewCoord.toDouble(),
until = starFieldSpecs.zFadeOutEnd.toDouble(),
).toFloat(),
color = Color(
red = Random.nextDouble(0.60, 0.66).toFloat(),
green = Random.nextDouble(0.60, 0.66).toFloat(),
blue = Random.nextDouble(0.97, 1.0).toFloat(),
),
)
}
}
}


private fun StarField.update(
timeInSecs: Float
): StarField =
copy(
stars = stars.map { star ->
val zNewCoord =
(star.zCoord + specs.speed * timeInSecs).takeIf { it < specs.zFadeOutEnd }
?: specs.zNewCoord
when (star) {
is Star.EntryStar -> star.copy(zCoord = zNewCoord)
is Star.RegularStar -> star.copy(zCoord = zNewCoord)
}
}.toImmutableList()
)

@Composable
fun StarFieldMessageBoard(
modifier: Modifier = Modifier,
) {
val starFieldSpecs = remember { StarFieldSpecs() }
var starField by remember { mutableStateOf(generateStars(starFieldSpecs)) }
LaunchedEffect(Unit) {
var lastFrame = 0L
while (isActive) {
withFrameMillis {
if (lastFrame == 0L) {
lastFrame = it
}
starField = starField.update(
timeInSecs = (it - lastFrame) / 1_000f,
)
lastFrame = it
}
}
}

StarFieldContent(
starField = starField,
modifier = modifier
.fillMaxSize()
.background(appyx_dark)
)
}

@Composable
private fun StarFieldContent(
starField: StarField,
modifier: Modifier = Modifier,
) {
Box(modifier = modifier) {
starField.stars.forEach { star ->
val zPos = star.zCoord
val xPos = star.xCoord * zPos
val yPos = star.yCoord * zPos
val alpha = smoothstep(starField.specs.zFadeInStart, starField.specs.zFadeInEnd, zPos) -
smoothstep(starField.specs.zFadeOutStart, starField.specs.zFadeOutEnd, zPos)

StarContent(
star,
modifier = Modifier
.scale(zPos)
.size(290.dp)
.aspectRatio(1.5f)
.align(BiasAlignment(xPos, yPos))
.alpha(alpha)
.zIndex(zPos)
)
}
}
}

@Composable
private fun StarContent(
star: Star,
modifier: Modifier = Modifier,
) {
when (star) {
is Star.EntryStar -> EntryStarContent(star, modifier)
is Star.RegularStar -> RegularStarContent(star, modifier)
}
}

@Composable
private fun EntryStarContent(
star: Star.EntryStar,
modifier: Modifier = Modifier,
) {
EntryCard(
entry = star.entry,
modifier = modifier
)
}

@Composable
private fun RegularStarContent(
star: Star.RegularStar,
modifier: Modifier = Modifier,
) {
Canvas(
modifier = modifier
) {
drawCircle(color = star.color, radius = density * 2f)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,21 @@ import com.bumble.appyx.utils.multiplatform.Parcelize
import com.bumble.puzzyx.appyx.component.backstackclipper.BackStackClipper
import com.bumble.puzzyx.composable.CallToActionScreen
import com.bumble.puzzyx.composable.MessageBoard
import com.bumble.puzzyx.composable.StarFieldMessageBoard
import com.bumble.puzzyx.model.Puzzle.PUZZLE1
import com.bumble.puzzyx.node.app.PuzzyxAppNode.NavTarget
import com.bumble.puzzyx.node.app.PuzzyxAppNode.NavTarget.CallToAction
import com.bumble.puzzyx.node.app.PuzzyxAppNode.NavTarget.MessageBoard
import com.bumble.puzzyx.node.app.PuzzyxAppNode.NavTarget.Puzzle1
import com.bumble.puzzyx.node.app.PuzzyxAppNode.NavTarget.StarFieldMessageBoard
import com.bumble.puzzyx.node.puzzle1.Puzzle1Node
import com.bumble.puzzyx.ui.DottedMeshShape

private val screens = listOf(
Puzzle1,
CallToAction,
MessageBoard
MessageBoard,
StarFieldMessageBoard,
)

class PuzzyxAppNode(
Expand All @@ -67,6 +70,9 @@ class PuzzyxAppNode(

@Parcelize
object MessageBoard : NavTarget()

@Parcelize
object StarFieldMessageBoard : NavTarget()
}


Expand All @@ -78,6 +84,7 @@ class PuzzyxAppNode(
)
is CallToAction -> node(buildContext) { modifier -> CallToActionScreen(modifier) }
is MessageBoard -> node(buildContext) { modifier -> MessageBoard(modifier) }
is StarFieldMessageBoard -> node(buildContext) { modifier -> StarFieldMessageBoard(modifier) }
}

@Composable
Expand Down

0 comments on commit 7fcbfd8

Please sign in to comment.