Change the roll duration animation & clean the theme associated with it.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2024-08-07 16:36:42 +02:00
parent 2f187a3dda
commit 041505bb79
6 changed files with 100 additions and 58 deletions

View file

@ -16,7 +16,6 @@ import com.pixelized.rplexicon.data.repository.character.ActionRepository
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.ItemsRepository import com.pixelized.rplexicon.data.repository.character.ItemsRepository
import com.pixelized.rplexicon.data.repository.character.ObjectActionRepository
import com.pixelized.rplexicon.data.repository.character.SkillRepository import com.pixelized.rplexicon.data.repository.character.SkillRepository
import com.pixelized.rplexicon.data.repository.character.SpellRepository import com.pixelized.rplexicon.data.repository.character.SpellRepository
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio

View file

@ -7,6 +7,8 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ContentTransform import androidx.compose.animation.ContentTransform
import androidx.compose.animation.SizeTransform import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
@ -102,6 +104,7 @@ fun RollOverlay(
isDarkTheme = isInDarkTheme(), isDarkTheme = isInDarkTheme(),
drawer = drawer, drawer = drawer,
dice = viewModel.dice, dice = viewModel.dice,
diceRotation = viewModel.diceRotation,
card = viewModel.card, card = viewModel.card,
groups = viewModel.alterations, groups = viewModel.alterations,
showDetail = viewModel.showDetail, showDetail = viewModel.showDetail,
@ -150,6 +153,7 @@ private fun RollOverlayContent(
isDarkTheme: Boolean, isDarkTheme: Boolean,
drawer: DrawerState, drawer: DrawerState,
dice: State<RollDiceUio?>, dice: State<RollDiceUio?>,
diceRotation: Animatable<Float, AnimationVector1D>,
card: State<ThrowsCardUio?>, card: State<ThrowsCardUio?>,
groups: State<List<AlterationGroupUio>>, groups: State<List<AlterationGroupUio>>,
showDetail: State<Boolean>, showDetail: State<Boolean>,
@ -288,6 +292,7 @@ private fun RollOverlayContent(
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
.detailPaddingBottom(showDetail = showDetail, bottom = 128.dp), .detailPaddingBottom(showDetail = showDetail, bottom = 128.dp),
dice = dice, dice = dice,
diceRotation = diceRotation,
onDice = onDice, onDice = onDice,
criticalSuccess = stringResource(id = R.string.roll_overlay__critical_success), criticalSuccess = stringResource(id = R.string.roll_overlay__critical_success),
criticalFailure = stringResource(id = R.string.roll_overlay__critical_failure), criticalFailure = stringResource(id = R.string.roll_overlay__critical_failure),
@ -365,6 +370,7 @@ private fun RollOverlayPreview(
isDarkTheme = isSystemInDarkTheme(), isDarkTheme = isSystemInDarkTheme(),
drawer = rememberDrawerState(initialValue = preview.drawer), drawer = rememberDrawerState(initialValue = preview.drawer),
dice = preview.dice, dice = preview.dice,
diceRotation = remember { Animatable(0f) },
card = preview.card, card = preview.card,
groups = rememberRollAlterations(), groups = rememberRollAlterations(),
showDetail = remember { mutableStateOf(true) }, showDetail = remember { mutableStateOf(true) },

View file

@ -1,6 +1,8 @@
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.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.spring 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
@ -35,9 +37,11 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope 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
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
@ -79,6 +83,7 @@ class RollOverlayViewModel @Inject constructor(
private var targetRoll = 0f 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
val diceRotation: Animatable<Float, AnimationVector1D> = Animatable(0f)
private val _card = mutableStateOf<ThrowsCardUio?>(null) private val _card = mutableStateOf<ThrowsCardUio?>(null)
val card: State<ThrowsCardUio?> get() = _card val card: State<ThrowsCardUio?> get() = _card
@ -93,6 +98,7 @@ class RollOverlayViewModel @Inject constructor(
val alterationDetailDialog: State<AlterationDialogDetailUio?> get() = _alterationDetailDialog val alterationDetailDialog: State<AlterationDialogDetailUio?> get() = _alterationDetailDialog
suspend fun prepareRoll(diceThrow: DiceThrow) { suspend fun prepareRoll(diceThrow: DiceThrow) {
this.targetRoll = 0f
this.diceThrow = diceThrow this.diceThrow = diceThrow
// hide the throw for other player. // hide the throw for other player.
_isThrowHidden.value = diceThrow is DiceThrow.DeathSavingThrow _isThrowHidden.value = diceThrow is DiceThrow.DeathSavingThrow
@ -148,36 +154,39 @@ class RollOverlayViewModel @Inject constructor(
fun roll(uiScope: CoroutineScope) { fun roll(uiScope: CoroutineScope) {
rollJob?.cancel() rollJob?.cancel()
rollJob = uiScope.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 }, )
if (result != null) {
// reset the displayed dice value
_dice.value = result.dice.copy(
result = null,
isCriticalSuccess = false,
isCriticalFailure = false,
) )
// Roll the d20 value. // play the roll animation
result?.dice?.let { launch {
_dice.value = _dice.value?.copy( targetRoll += 360f * 4
result = null diceRotation.animateTo(
targetValue = targetRoll,
animationSpec = spring(
dampingRatio = 0.9f,
stiffness = 105f,
),
) )
_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 { // wait a fix amount of time then
delay(RollDiceUio.ROLL_DURATION.toLong())
// display the roll result & share with other player
if (isActive) {
_dice.value = result.dice
// share the result // share the result
throwRepository.sendThrow(sheet?.name, it) throwRepository.sendThrow(sheet?.name, result.throws)
// Display the roll card. // Display the roll card.
_card.value = throwCardFactory.convert(it) _card.value = throwCardFactory.convert(result.throws)
} }
} }
} }

View file

@ -15,7 +15,6 @@ import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically 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.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -35,9 +34,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -56,13 +55,12 @@ 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: 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,
) { ) {
companion object { companion object{
const val ROLL_DURATION = 750 const val ROLL_DURATION = 500
} }
} }
@ -71,6 +69,7 @@ fun RollDice(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
diceSize: Dp = 64.dp * 2.5f, diceSize: Dp = 64.dp * 2.5f,
dice: State<RollDiceUio?>, dice: State<RollDiceUio?>,
diceRotation: Animatable<Float, AnimationVector1D>,
criticalSuccess: String, criticalSuccess: String,
criticalFailure: String, criticalFailure: String,
onDice: () -> Unit, onDice: () -> Unit,
@ -90,7 +89,10 @@ fun RollDice(
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
indication = null, indication = null,
onClick = onDice, onClick = onDice,
), )
.graphicsLayer {
this.rotationZ = diceRotation.value % 360
},
dice = dice, dice = dice,
) )
Result( Result(
@ -114,21 +116,23 @@ private fun Dice(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
dice: State<RollDiceUio?>, dice: State<RollDiceUio?>,
) { ) {
dice.value?.let { val colorScheme = MaterialTheme.colorScheme
val time = rememberTimeState() val time = rememberTimeState()
val transition = animateFloatAsState( val transition = animateFloatAsState(
targetValue = if (it.isCriticalSuccess) 1.0f else 0f, targetValue = if (dice.value?.isCriticalSuccess == true) 1.0f else 0f,
label = "animatedCritical", label = "animatedCritical",
animationSpec = tween(1500), animationSpec = tween(1500),
)
val localModifier = remember {
modifier.dancingColor(
time = time,
transition = transition,
default = colorScheme.primary,
) )
}
dice.value?.let {
Image( Image(
modifier = modifier modifier = localModifier,
.rotate(degrees = it.rotation.value % 360)
.dancingColor(
time = time,
transition = transition,
default = MaterialTheme.colorScheme.primary,
),
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.primary), colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.primary),
painter = painterResource(id = it.icon), painter = painterResource(id = it.icon),
contentDescription = null, contentDescription = null,
@ -142,9 +146,7 @@ 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() + val enter = fadeIn() +
@ -167,7 +169,7 @@ private fun Result(
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
Text( Text(
style = MaterialTheme.typography.displayMedium, style = MaterialTheme.lexicon.typography.diceRoll,
text = it, text = it,
) )
} }
@ -195,8 +197,8 @@ private fun Critical(
modifier = modifier, modifier = modifier,
targetState = state.value, targetState = state.value,
transitionSpec = { transitionSpec = {
val enter = fadeIn() + expandIn(expandFrom = Alignment.Center) { it } val enter = fadeIn()
val exit = fadeOut() + shrinkOut(shrinkTowards = Alignment.Center) { IntSize.Zero } val exit = fadeOut()
enter togetherWith exit enter togetherWith exit
}, },
label = "CriticalAnimation", label = "CriticalAnimation",
@ -230,13 +232,13 @@ private fun SkillRollPreview(
mutableStateOf( mutableStateOf(
RollDiceUio( RollDiceUio(
icon = R.drawable.ic_d20_24, icon = R.drawable.ic_d20_24,
rotation = Animatable(0f),
isCriticalSuccess = preview == 20, isCriticalSuccess = preview == 20,
isCriticalFailure = preview == 1, isCriticalFailure = preview == 1,
result = "$preview", result = "$preview",
) )
) )
}, },
diceRotation = remember { Animatable(0f) },
criticalSuccess = "CRITICAL SUCCESS", criticalSuccess = "CRITICAL SUCCESS",
criticalFailure = "CRITICAL FAILURE", criticalFailure = "CRITICAL FAILURE",
onDice = { }, onDice = { },

View file

@ -55,7 +55,7 @@ fun LexiconTheme(
colorScheme = colorScheme, colorScheme = colorScheme,
shapes = lexiconShapes(), shapes = lexiconShapes(),
dimens = lexiconDimen(density = density), dimens = lexiconDimen(density = density),
typography = lexiconTypography(colorScheme = colorScheme), typography = lexiconTypography(isDarkTheme = isDarkTheme, colorScheme = colorScheme),
) )
} }

View file

@ -2,6 +2,8 @@ package com.pixelized.rplexicon.ui.theme.typography
import androidx.compose.material3.Typography import androidx.compose.material3.Typography
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.Font
@ -35,6 +37,7 @@ val georgia = FontFamily(
data class LexiconTypography( data class LexiconTypography(
val base: Typography, val base: Typography,
val stamp: TextStyle, val stamp: TextStyle,
val diceRoll: TextStyle,
val dropCap: DropCapTypography, val dropCap: DropCapTypography,
val detail: Detail, val detail: Detail,
val search: Search, val search: Search,
@ -176,11 +179,21 @@ data class LexiconTypography(
fun lexiconTypography( fun lexiconTypography(
colorScheme: LexiconColors, colorScheme: LexiconColors,
isDarkTheme: Boolean,
base: Typography = Typography(), base: Typography = Typography(),
dropCap: LexiconTypography.DropCapTypography = LexiconTypography.DropCapTypography(base), dropCap: LexiconTypography.DropCapTypography = LexiconTypography.DropCapTypography(base),
stamp: TextStyle = base.headlineLarge.copy( stamp: TextStyle = base.headlineLarge.copy(
fontFamily = stampFontFamily, fontFamily = stampFontFamily,
), ),
diceRoll: TextStyle = base.displayMedium.copy(
shadow = when (isDarkTheme) {
true -> Shadow(
offset = Offset(4f, 4f),
blurRadius = 8f,
)
else -> null
}
),
detail: LexiconTypography.Detail = LexiconTypography.Detail( detail: LexiconTypography.Detail = LexiconTypography.Detail(
highlightStyle = SpanStyle(color = colorScheme.search.highlight), highlightStyle = SpanStyle(color = colorScheme.search.highlight),
), ),
@ -254,20 +267,33 @@ fun lexiconTypography(
paragraph = dropCap.bodyMedium, paragraph = dropCap.bodyMedium,
) )
) )
): LexiconTypography = LexiconTypography(
)
: LexiconTypography = LexiconTypography(
base = base, base = base,
dropCap = dropCap, dropCap = dropCap,
stamp = stamp, stamp = stamp,
diceRoll = diceRoll,
detail = detail, detail = detail,
search = search, search = search,
adventure = adventure, adventure = adventure,
) )
private fun TextStyle.toDropCapSpan( private fun TextStyle.toDropCapSpan(
sizeRatio: Float = 1.5f, sizeRatio: Float =
antiLetterSpacing: TextUnit? = null,
baselineShift: BaselineShift? = null, 1.5f,
): SpanStyle { antiLetterSpacing: TextUnit
?
= null,
baselineShift: BaselineShift
?
= null,
)
: SpanStyle {
return copy( return copy(
fontFamily = angloText, fontFamily = angloText,
fontSize = fontSize * sizeRatio, fontSize = fontSize * sizeRatio,