Dice roll refactor with Animatable.
This commit is contained in:
parent
039f124f88
commit
87c6a3f20f
4 changed files with 60 additions and 55 deletions
|
|
@ -116,7 +116,7 @@ fun RollOverlay(
|
||||||
overlay.hideOverlay()
|
overlay.hideOverlay()
|
||||||
},
|
},
|
||||||
onDice = {
|
onDice = {
|
||||||
viewModel.roll()
|
viewModel.roll(uiScope = scope)
|
||||||
},
|
},
|
||||||
onCard = {
|
onCard = {
|
||||||
viewModel.toggleCardDetail()
|
viewModel.toggleCardDetail()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.pixelized.rplexicon.ui.screens.rolls
|
package com.pixelized.rplexicon.ui.screens.rolls
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import androidx.compose.animation.core.spring
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
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.Description
|
||||||
import com.pixelized.rplexicon.data.model.DiceThrow
|
import com.pixelized.rplexicon.data.model.DiceThrow
|
||||||
import com.pixelized.rplexicon.data.model.alteration.AlterationStatus
|
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.ActiveAlterationRepository
|
||||||
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
|
import com.pixelized.rplexicon.data.repository.character.AlterationRepository
|
||||||
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
|
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
|
||||||
import com.pixelized.rplexicon.data.repository.character.DescriptionRepository
|
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.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.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.RollDiceUio
|
||||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowCardFactory
|
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowCardFactory
|
||||||
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio
|
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.context
|
||||||
import com.pixelized.rplexicon.utilitary.extentions.switch
|
import com.pixelized.rplexicon.utilitary.extentions.switch
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
@ -73,6 +74,7 @@ class RollOverlayViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}.collectAsState(initial = emptyList())
|
}.collectAsState(initial = emptyList())
|
||||||
|
|
||||||
|
private var targetRoll = 0f
|
||||||
private val _dice = mutableStateOf<RollDiceUio?>(null)
|
private val _dice = mutableStateOf<RollDiceUio?>(null)
|
||||||
val dice: State<RollDiceUio?> get() = _dice
|
val dice: State<RollDiceUio?> get() = _dice
|
||||||
|
|
||||||
|
|
@ -141,34 +143,40 @@ class RollOverlayViewModel @Inject constructor(
|
||||||
_alterationStatusOverride.value = override
|
_alterationStatusOverride.value = override
|
||||||
}
|
}
|
||||||
|
|
||||||
fun roll() {
|
fun roll(uiScope: CoroutineScope) {
|
||||||
diceThrow.let { diceThrow ->
|
|
||||||
rollJob?.cancel()
|
rollJob?.cancel()
|
||||||
rollJob = viewModelScope.launch {
|
rollJob = uiScope.launch {
|
||||||
|
diceThrow.let { diceThrow ->
|
||||||
// roll the dice ;)
|
// roll the dice ;)
|
||||||
val result = rollUseCase.roll(
|
val result = rollUseCase.roll(
|
||||||
diceThrow = diceThrow,
|
diceThrow = diceThrow,
|
||||||
isThrowHidden = _isThrowHidden.value,
|
isThrowHidden = _isThrowHidden.value,
|
||||||
alterationId = _alterations.value.mapNotNull { if (it.checked) it.label else null },
|
alterationId = _alterations.value.mapNotNull { if (it.checked) it.label else null },
|
||||||
)
|
)
|
||||||
// share the result
|
// Roll the d20 value.
|
||||||
result?.throws?.let {
|
result?.dice?.let {
|
||||||
firebaseRepository.sendThrow(sheet?.name, it)
|
_dice.value = _dice.value?.copy(
|
||||||
}
|
result = null
|
||||||
// Start the roll animation.
|
)
|
||||||
_dice.value = _dice.value?.let {
|
_dice.value?.rotation?.let { rotation ->
|
||||||
it.copy(
|
targetRoll += 360f * 4
|
||||||
result = "",
|
rotation.animateTo(
|
||||||
animationSpec = RollDiceUio.TWEEN,
|
targetValue = targetRoll,
|
||||||
rotation = it.rotation + 5f,
|
animationSpec = spring(
|
||||||
|
dampingRatio = 0.9f,
|
||||||
|
stiffness = 105f,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
delay(RollDiceUio.ROLL_DURATION)
|
_dice.value = it
|
||||||
// Roll the d20 value.
|
targetRoll = 0f
|
||||||
result?.dice?.let { _dice.value = it }
|
}
|
||||||
delay(250)
|
result?.throws?.let {
|
||||||
|
// share the result
|
||||||
|
firebaseRepository.sendThrow(sheet?.name, it)
|
||||||
// Display the roll card.
|
// Display the roll card.
|
||||||
result?.throws?.let { _card.value = throwCardFactory.convert(it) }
|
_card.value = throwCardFactory.convert(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,17 @@ package com.pixelized.rplexicon.ui.screens.rolls.composable
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.core.AnimationSpec
|
import androidx.compose.animation.SizeTransform
|
||||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
import androidx.compose.animation.core.Animatable
|
||||||
import androidx.compose.animation.core.SnapSpec
|
import androidx.compose.animation.core.AnimationVector1D
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.expandIn
|
import androidx.compose.animation.expandIn
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.shrinkOut
|
import androidx.compose.animation.shrinkOut
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.animation.togetherWith
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
|
@ -54,21 +56,13 @@ import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||||
@Stable
|
@Stable
|
||||||
data class RollDiceUio(
|
data class RollDiceUio(
|
||||||
@DrawableRes val icon: Int = R.drawable.ic_d20_24,
|
@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 isCriticalSuccess: Boolean = false,
|
||||||
val isCriticalFailure: Boolean = false,
|
val isCriticalFailure: Boolean = false,
|
||||||
val result: String? = null,
|
val result: String? = null,
|
||||||
val animationSpec: AnimationSpec<Float> = SNAP,
|
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val ROLL_DURATION = 750L
|
const val ROLL_DURATION = 750
|
||||||
|
|
||||||
val SNAP = SnapSpec<Float>()
|
|
||||||
|
|
||||||
val TWEEN = tween<Float>(
|
|
||||||
durationMillis = ROLL_DURATION.toInt(),
|
|
||||||
easing = LinearOutSlowInEasing,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,11 +116,6 @@ private fun Dice(
|
||||||
) {
|
) {
|
||||||
dice.value?.let {
|
dice.value?.let {
|
||||||
val time = rememberTimeState()
|
val time = rememberTimeState()
|
||||||
val animatedRotation = animateFloatAsState(
|
|
||||||
targetValue = it.rotation * 360,
|
|
||||||
animationSpec = it.animationSpec,
|
|
||||||
label = "AnimatedRotation"
|
|
||||||
)
|
|
||||||
val transition = animateFloatAsState(
|
val transition = animateFloatAsState(
|
||||||
targetValue = if (it.isCriticalSuccess) 1.0f else 0f,
|
targetValue = if (it.isCriticalSuccess) 1.0f else 0f,
|
||||||
label = "animatedCritical",
|
label = "animatedCritical",
|
||||||
|
|
@ -134,7 +123,7 @@ private fun Dice(
|
||||||
)
|
)
|
||||||
Image(
|
Image(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.rotate(degrees = animatedRotation.value % 360)
|
.rotate(degrees = it.rotation.value % 360)
|
||||||
.dancingColor(
|
.dancingColor(
|
||||||
time = time,
|
time = time,
|
||||||
transition = transition,
|
transition = transition,
|
||||||
|
|
@ -153,19 +142,28 @@ private fun Result(
|
||||||
dice: State<RollDiceUio?>,
|
dice: State<RollDiceUio?>,
|
||||||
) {
|
) {
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
modifier = modifier,
|
modifier = Modifier
|
||||||
|
.background(brush = MaterialTheme.lexicon.colorScheme.dice.valueBackground)
|
||||||
|
.then(other = modifier),
|
||||||
targetState = dice.value?.result,
|
targetState = dice.value?.result,
|
||||||
transitionSpec = {
|
transitionSpec = {
|
||||||
val enter = fadeIn() + expandIn(expandFrom = Alignment.Center) { it }
|
val enter = fadeIn() +
|
||||||
val exit = fadeOut() + shrinkOut(shrinkTowards = Alignment.Center) { IntSize.Zero }
|
expandIn(expandFrom = Alignment.Center) { it } +
|
||||||
enter togetherWith exit
|
slideInVertically { it / 5 }
|
||||||
|
val exit = fadeOut() +
|
||||||
|
shrinkOut(shrinkTowards = Alignment.Center) { IntSize.Zero } +
|
||||||
|
slideOutVertically { -it / 5 }
|
||||||
|
enter togetherWith exit using SizeTransform(clip = false)
|
||||||
},
|
},
|
||||||
label = "ResultAnimation"
|
label = "ResultAnimation"
|
||||||
) {
|
) {
|
||||||
when (it) {
|
when (it) {
|
||||||
null -> Box(modifier = Modifier)
|
null -> Box(
|
||||||
|
modifier = Modifier,
|
||||||
|
)
|
||||||
|
|
||||||
else -> Box(
|
else -> Box(
|
||||||
modifier = Modifier.background(brush = MaterialTheme.lexicon.colorScheme.dice.valueBackground),
|
modifier = Modifier,
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -232,7 +230,7 @@ private fun SkillRollPreview(
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
RollDiceUio(
|
RollDiceUio(
|
||||||
icon = R.drawable.ic_d20_24,
|
icon = R.drawable.ic_d20_24,
|
||||||
rotation = 0f,
|
rotation = Animatable(0f),
|
||||||
isCriticalSuccess = preview == 20,
|
isCriticalSuccess = preview == 20,
|
||||||
isCriticalFailure = preview == 1,
|
isCriticalFailure = preview == 1,
|
||||||
result = "$preview",
|
result = "$preview",
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
|
@ -153,7 +152,7 @@ private fun ClassHeaderDice(
|
||||||
else -> MaterialTheme.colorScheme.onSurface
|
else -> MaterialTheme.colorScheme.onSurface
|
||||||
},
|
},
|
||||||
animationSpec = tween(
|
animationSpec = tween(
|
||||||
delayMillis = RollDiceUio.ROLL_DURATION.toInt()
|
delayMillis = RollDiceUio.ROLL_DURATION,
|
||||||
),
|
),
|
||||||
label = "ClassHeaderDiceColorAnimation"
|
label = "ClassHeaderDiceColorAnimation"
|
||||||
)
|
)
|
||||||
|
|
@ -197,12 +196,12 @@ private fun ClassHeaderDice(
|
||||||
private fun labelAnimationSpec(): AnimatedContentTransitionScope<*>.() -> ContentTransform = {
|
private fun labelAnimationSpec(): AnimatedContentTransitionScope<*>.() -> ContentTransform = {
|
||||||
val slideIn = slideInVertically(
|
val slideIn = slideInVertically(
|
||||||
animationSpec = tween(
|
animationSpec = tween(
|
||||||
delayMillis = RollDiceUio.ROLL_DURATION.toInt()
|
delayMillis = RollDiceUio.ROLL_DURATION
|
||||||
)
|
)
|
||||||
) { it / 2 }
|
) { it / 2 }
|
||||||
val fadeIn = fadeIn(
|
val fadeIn = fadeIn(
|
||||||
animationSpec = tween(
|
animationSpec = tween(
|
||||||
delayMillis = RollDiceUio.ROLL_DURATION.toInt()
|
delayMillis = RollDiceUio.ROLL_DURATION
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val slideOut = slideOutVertically(
|
val slideOut = slideOutVertically(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue