From 041505bb79f9d48408692765f9c0ff2567ca5898 Mon Sep 17 00:00:00 2001 From: "Andres Gomez, Thomas (ITDV RL)" Date: Wed, 7 Aug 2024 16:36:42 +0200 Subject: [PATCH] Change the roll duration animation & clean the theme associated with it. --- .../rplexicon/business/DiceThrowUseCase.kt | 1 - .../rplexicon/ui/screens/rolls/RollOverlay.kt | 6 ++ .../ui/screens/rolls/RollOverlayViewModel.kt | 59 +++++++++++-------- .../ui/screens/rolls/composable/RollDice.kt | 54 +++++++++-------- .../com/pixelized/rplexicon/ui/theme/Theme.kt | 2 +- .../ui/theme/typography/LexiconTypography.kt | 36 +++++++++-- 6 files changed, 100 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/com/pixelized/rplexicon/business/DiceThrowUseCase.kt b/app/src/main/java/com/pixelized/rplexicon/business/DiceThrowUseCase.kt index 28f0557..c683790 100644 --- a/app/src/main/java/com/pixelized/rplexicon/business/DiceThrowUseCase.kt +++ b/app/src/main/java/com/pixelized/rplexicon/business/DiceThrowUseCase.kt @@ -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 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 751c7aa..ab0d9a2 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 @@ -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, + diceRotation: Animatable, card: State, groups: State>, showDetail: State, @@ -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) }, 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 3a7f7ab..c7d87e4 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,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(null) val dice: State get() = _dice + val diceRotation: Animatable = Animatable(0f) private val _card = mutableStateOf(null) val card: State get() = _card @@ -93,6 +98,7 @@ class RollOverlayViewModel @Inject constructor( val alterationDetailDialog: State 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) } } } 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 39687ee..3cad725 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 @@ -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 = 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, + diceRotation: Animatable, 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, ) { - 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, ) { 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 = { }, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/theme/Theme.kt b/app/src/main/java/com/pixelized/rplexicon/ui/theme/Theme.kt index 8c1f85f..374d615 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/theme/Theme.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/theme/Theme.kt @@ -55,7 +55,7 @@ fun LexiconTheme( colorScheme = colorScheme, shapes = lexiconShapes(), dimens = lexiconDimen(density = density), - typography = lexiconTypography(colorScheme = colorScheme), + typography = lexiconTypography(isDarkTheme = isDarkTheme, colorScheme = colorScheme), ) } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/theme/typography/LexiconTypography.kt b/app/src/main/java/com/pixelized/rplexicon/ui/theme/typography/LexiconTypography.kt index 10dc4e4..25bcd3a 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/theme/typography/LexiconTypography.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/theme/typography/LexiconTypography.kt @@ -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,