diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt index a4f2639..751c7aa 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlay.kt @@ -116,7 +116,7 @@ fun RollOverlay( overlay.hideOverlay() }, onDice = { - viewModel.roll() + viewModel.roll(uiScope = scope) }, onCard = { viewModel.toggleCardDetail() diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlayViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlayViewModel.kt index 05c760b..72a7af5 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlayViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlayViewModel.kt @@ -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(null) val dice: State 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) + } } } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt index e251e2c..39687ee 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt @@ -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 = Animatable(0f), val isCriticalSuccess: Boolean = false, val isCriticalFailure: Boolean = false, val result: String? = null, - val animationSpec: AnimationSpec = SNAP, ) { companion object { - const val ROLL_DURATION = 750L - - val SNAP = SnapSpec() - - val TWEEN = tween( - 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, ) { 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", diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/summary/composable/ClassHeaderSummary.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/summary/composable/ClassHeaderSummary.kt index f8147a2..eae180c 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/summary/composable/ClassHeaderSummary.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/summary/composable/ClassHeaderSummary.kt @@ -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(