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

View file

@ -1,6 +1,8 @@
package com.pixelized.rplexicon.ui.screens.rolls
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.runtime.Composable
import androidx.compose.runtime.State
@ -35,9 +37,11 @@ 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
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@ -79,6 +83,7 @@ class RollOverlayViewModel @Inject constructor(
private var targetRoll = 0f
private val _dice = mutableStateOf<RollDiceUio?>(null)
val dice: State<RollDiceUio?> get() = _dice
val diceRotation: Animatable<Float, AnimationVector1D> = Animatable(0f)
private val _card = mutableStateOf<ThrowsCardUio?>(null)
val card: State<ThrowsCardUio?> get() = _card
@ -93,6 +98,7 @@ class RollOverlayViewModel @Inject constructor(
val alterationDetailDialog: State<AlterationDialogDetailUio?> get() = _alterationDetailDialog
suspend fun prepareRoll(diceThrow: DiceThrow) {
this.targetRoll = 0f
this.diceThrow = diceThrow
// hide the throw for other player.
_isThrowHidden.value = diceThrow is DiceThrow.DeathSavingThrow
@ -148,36 +154,39 @@ class RollOverlayViewModel @Inject constructor(
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 },
// roll the dice ;)
val result = rollUseCase.roll(
diceThrow = diceThrow,
isThrowHidden = _isThrowHidden.value,
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.
result?.dice?.let {
_dice.value = _dice.value?.copy(
result = null
// play the roll animation
launch {
targetRoll += 360f * 4
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
throwRepository.sendThrow(sheet?.name, it)
throwRepository.sendThrow(sheet?.name, result.throws)
// 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.togetherWith
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
@ -35,9 +34,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@ -56,13 +55,12 @@ import com.pixelized.rplexicon.utilitary.extentions.lexicon
@Stable
data class RollDiceUio(
@DrawableRes val icon: Int = R.drawable.ic_d20_24,
val rotation: Animatable<Float, AnimationVector1D> = Animatable(0f),
val isCriticalSuccess: Boolean = false,
val isCriticalFailure: Boolean = false,
val result: String? = null,
) {
companion object {
const val ROLL_DURATION = 750
companion object{
const val ROLL_DURATION = 500
}
}
@ -71,6 +69,7 @@ fun RollDice(
modifier: Modifier = Modifier,
diceSize: Dp = 64.dp * 2.5f,
dice: State<RollDiceUio?>,
diceRotation: Animatable<Float, AnimationVector1D>,
criticalSuccess: String,
criticalFailure: String,
onDice: () -> Unit,
@ -90,7 +89,10 @@ fun RollDice(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = onDice,
),
)
.graphicsLayer {
this.rotationZ = diceRotation.value % 360
},
dice = dice,
)
Result(
@ -114,21 +116,23 @@ private fun Dice(
modifier: Modifier = Modifier,
dice: State<RollDiceUio?>,
) {
dice.value?.let {
val time = rememberTimeState()
val transition = animateFloatAsState(
targetValue = if (it.isCriticalSuccess) 1.0f else 0f,
label = "animatedCritical",
animationSpec = tween(1500),
val colorScheme = MaterialTheme.colorScheme
val time = rememberTimeState()
val transition = animateFloatAsState(
targetValue = if (dice.value?.isCriticalSuccess == true) 1.0f else 0f,
label = "animatedCritical",
animationSpec = tween(1500),
)
val localModifier = remember {
modifier.dancingColor(
time = time,
transition = transition,
default = colorScheme.primary,
)
}
dice.value?.let {
Image(
modifier = modifier
.rotate(degrees = it.rotation.value % 360)
.dancingColor(
time = time,
transition = transition,
default = MaterialTheme.colorScheme.primary,
),
modifier = localModifier,
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.primary),
painter = painterResource(id = it.icon),
contentDescription = null,
@ -142,9 +146,7 @@ private fun Result(
dice: State<RollDiceUio?>,
) {
AnimatedContent(
modifier = Modifier
.background(brush = MaterialTheme.lexicon.colorScheme.dice.valueBackground)
.then(other = modifier),
modifier = modifier,
targetState = dice.value?.result,
transitionSpec = {
val enter = fadeIn() +
@ -167,7 +169,7 @@ private fun Result(
contentAlignment = Alignment.Center,
) {
Text(
style = MaterialTheme.typography.displayMedium,
style = MaterialTheme.lexicon.typography.diceRoll,
text = it,
)
}
@ -195,8 +197,8 @@ private fun Critical(
modifier = modifier,
targetState = state.value,
transitionSpec = {
val enter = fadeIn() + expandIn(expandFrom = Alignment.Center) { it }
val exit = fadeOut() + shrinkOut(shrinkTowards = Alignment.Center) { IntSize.Zero }
val enter = fadeIn()
val exit = fadeOut()
enter togetherWith exit
},
label = "CriticalAnimation",
@ -230,13 +232,13 @@ private fun SkillRollPreview(
mutableStateOf(
RollDiceUio(
icon = R.drawable.ic_d20_24,
rotation = Animatable(0f),
isCriticalSuccess = preview == 20,
isCriticalFailure = preview == 1,
result = "$preview",
)
)
},
diceRotation = remember { Animatable(0f) },
criticalSuccess = "CRITICAL SUCCESS",
criticalFailure = "CRITICAL FAILURE",
onDice = { },

View file

@ -55,7 +55,7 @@ fun LexiconTheme(
colorScheme = colorScheme,
shapes = lexiconShapes(),
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.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.TextStyle
import androidx.compose.ui.text.font.Font
@ -35,6 +37,7 @@ val georgia = FontFamily(
data class LexiconTypography(
val base: Typography,
val stamp: TextStyle,
val diceRoll: TextStyle,
val dropCap: DropCapTypography,
val detail: Detail,
val search: Search,
@ -176,11 +179,21 @@ data class LexiconTypography(
fun lexiconTypography(
colorScheme: LexiconColors,
isDarkTheme: Boolean,
base: Typography = Typography(),
dropCap: LexiconTypography.DropCapTypography = LexiconTypography.DropCapTypography(base),
stamp: TextStyle = base.headlineLarge.copy(
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(
highlightStyle = SpanStyle(color = colorScheme.search.highlight),
),
@ -254,20 +267,33 @@ fun lexiconTypography(
paragraph = dropCap.bodyMedium,
)
)
): LexiconTypography = LexiconTypography(
)
: LexiconTypography = LexiconTypography(
base = base,
dropCap = dropCap,
stamp = stamp,
diceRoll = diceRoll,
detail = detail,
search = search,
adventure = adventure,
)
private fun TextStyle.toDropCapSpan(
sizeRatio: Float = 1.5f,
antiLetterSpacing: TextUnit? = null,
baselineShift: BaselineShift? = null,
): SpanStyle {
sizeRatio: Float =
1.5f,
antiLetterSpacing: TextUnit
?
= null,
baselineShift: BaselineShift
?
= null,
)
: SpanStyle {
return copy(
fontFamily = angloText,
fontSize = fontSize * sizeRatio,