Dice roll refactor with Animatable.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2024-07-12 11:11:18 +02:00
parent 039f124f88
commit 87c6a3f20f
4 changed files with 60 additions and 55 deletions

View file

@ -116,7 +116,7 @@ fun RollOverlay(
overlay.hideOverlay()
},
onDice = {
viewModel.roll()
viewModel.roll(uiScope = scope)
},
onCard = {
viewModel.toggleCardDetail()

View file

@ -1,6 +1,7 @@
package com.pixelized.rplexicon.ui.screens.rolls
import android.app.Application
import androidx.compose.animation.core.spring
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
@ -14,14 +15,14 @@ import com.pixelized.rplexicon.data.model.CharacterSheet
import com.pixelized.rplexicon.data.model.Description
import com.pixelized.rplexicon.data.model.DiceThrow
import com.pixelized.rplexicon.data.model.alteration.AlterationStatus
import com.pixelized.rplexicon.data.repository.firebase.RealtimeDatabaseRepository
import com.pixelized.rplexicon.data.repository.character.ActiveAlterationRepository
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
import com.pixelized.rplexicon.data.repository.character.DescriptionRepository
import com.pixelized.rplexicon.data.repository.firebase.RealtimeDatabaseRepository
import com.pixelized.rplexicon.ui.screens.character.composable.actions.AlterationItemUio
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationGroupUio
import com.pixelized.rplexicon.ui.screens.character.composable.dialogs.AlterationDialogDetailUio
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationGroupUio
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowCardFactory
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio
@ -30,9 +31,9 @@ import com.pixelized.rplexicon.ui.screens.rolls.factory.DiceFactory
import com.pixelized.rplexicon.utilitary.extentions.context
import com.pixelized.rplexicon.utilitary.extentions.switch
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
@ -73,6 +74,7 @@ class RollOverlayViewModel @Inject constructor(
}
}.collectAsState(initial = emptyList())
private var targetRoll = 0f
private val _dice = mutableStateOf<RollDiceUio?>(null)
val dice: State<RollDiceUio?> get() = _dice
@ -141,34 +143,40 @@ class RollOverlayViewModel @Inject constructor(
_alterationStatusOverride.value = override
}
fun roll() {
diceThrow.let { diceThrow ->
rollJob?.cancel()
rollJob = viewModelScope.launch {
fun roll(uiScope: CoroutineScope) {
rollJob?.cancel()
rollJob = uiScope.launch {
diceThrow.let { diceThrow ->
// roll the dice ;)
val result = rollUseCase.roll(
diceThrow = diceThrow,
isThrowHidden = _isThrowHidden.value,
alterationId = _alterations.value.mapNotNull { if (it.checked) it.label else null },
)
// share the result
result?.throws?.let {
firebaseRepository.sendThrow(sheet?.name, it)
}
// Start the roll animation.
_dice.value = _dice.value?.let {
it.copy(
result = "",
animationSpec = RollDiceUio.TWEEN,
rotation = it.rotation + 5f,
)
}
delay(RollDiceUio.ROLL_DURATION)
// Roll the d20 value.
result?.dice?.let { _dice.value = it }
delay(250)
// Display the roll card.
result?.throws?.let { _card.value = throwCardFactory.convert(it) }
result?.dice?.let {
_dice.value = _dice.value?.copy(
result = null
)
_dice.value?.rotation?.let { rotation ->
targetRoll += 360f * 4
rotation.animateTo(
targetValue = targetRoll,
animationSpec = spring(
dampingRatio = 0.9f,
stiffness = 105f,
),
)
}
_dice.value = it
targetRoll = 0f
}
result?.throws?.let {
// share the result
firebaseRepository.sendThrow(sheet?.name, it)
// Display the roll card.
_card.value = throwCardFactory.convert(it)
}
}
}
}

View file

@ -2,15 +2,17 @@ package com.pixelized.rplexicon.ui.screens.rolls.composable
import androidx.annotation.DrawableRes
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.SnapSpec
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandIn
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@ -54,21 +56,13 @@ import com.pixelized.rplexicon.utilitary.extentions.lexicon
@Stable
data class RollDiceUio(
@DrawableRes val icon: Int = R.drawable.ic_d20_24,
val rotation: Float = 0f,
val rotation: Animatable<Float, AnimationVector1D> = Animatable(0f),
val isCriticalSuccess: Boolean = false,
val isCriticalFailure: Boolean = false,
val result: String? = null,
val animationSpec: AnimationSpec<Float> = SNAP,
) {
companion object {
const val ROLL_DURATION = 750L
val SNAP = SnapSpec<Float>()
val TWEEN = tween<Float>(
durationMillis = ROLL_DURATION.toInt(),
easing = LinearOutSlowInEasing,
)
const val ROLL_DURATION = 750
}
}
@ -122,11 +116,6 @@ private fun Dice(
) {
dice.value?.let {
val time = rememberTimeState()
val animatedRotation = animateFloatAsState(
targetValue = it.rotation * 360,
animationSpec = it.animationSpec,
label = "AnimatedRotation"
)
val transition = animateFloatAsState(
targetValue = if (it.isCriticalSuccess) 1.0f else 0f,
label = "animatedCritical",
@ -134,7 +123,7 @@ private fun Dice(
)
Image(
modifier = modifier
.rotate(degrees = animatedRotation.value % 360)
.rotate(degrees = it.rotation.value % 360)
.dancingColor(
time = time,
transition = transition,
@ -153,19 +142,28 @@ private fun Result(
dice: State<RollDiceUio?>,
) {
AnimatedContent(
modifier = modifier,
modifier = Modifier
.background(brush = MaterialTheme.lexicon.colorScheme.dice.valueBackground)
.then(other = modifier),
targetState = dice.value?.result,
transitionSpec = {
val enter = fadeIn() + expandIn(expandFrom = Alignment.Center) { it }
val exit = fadeOut() + shrinkOut(shrinkTowards = Alignment.Center) { IntSize.Zero }
enter togetherWith exit
val enter = fadeIn() +
expandIn(expandFrom = Alignment.Center) { it } +
slideInVertically { it / 5 }
val exit = fadeOut() +
shrinkOut(shrinkTowards = Alignment.Center) { IntSize.Zero } +
slideOutVertically { -it / 5 }
enter togetherWith exit using SizeTransform(clip = false)
},
label = "ResultAnimation"
) {
when (it) {
null -> Box(modifier = Modifier)
null -> Box(
modifier = Modifier,
)
else -> Box(
modifier = Modifier.background(brush = MaterialTheme.lexicon.colorScheme.dice.valueBackground),
modifier = Modifier,
contentAlignment = Alignment.Center,
) {
Text(
@ -232,7 +230,7 @@ private fun SkillRollPreview(
mutableStateOf(
RollDiceUio(
icon = R.drawable.ic_d20_24,
rotation = 0f,
rotation = Animatable(0f),
isCriticalSuccess = preview == 20,
isCriticalFailure = preview == 1,
result = "$preview",

View file

@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
@ -153,7 +152,7 @@ private fun ClassHeaderDice(
else -> MaterialTheme.colorScheme.onSurface
},
animationSpec = tween(
delayMillis = RollDiceUio.ROLL_DURATION.toInt()
delayMillis = RollDiceUio.ROLL_DURATION,
),
label = "ClassHeaderDiceColorAnimation"
)
@ -197,12 +196,12 @@ private fun ClassHeaderDice(
private fun labelAnimationSpec(): AnimatedContentTransitionScope<*>.() -> ContentTransform = {
val slideIn = slideInVertically(
animationSpec = tween(
delayMillis = RollDiceUio.ROLL_DURATION.toInt()
delayMillis = RollDiceUio.ROLL_DURATION
)
) { it / 2 }
val fadeIn = fadeIn(
animationSpec = tween(
delayMillis = RollDiceUio.ROLL_DURATION.toInt()
delayMillis = RollDiceUio.ROLL_DURATION
)
)
val slideOut = slideOutVertically(