Add the "know what you roll" feature, allow automation after a roll.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2024-08-20 14:44:52 +02:00
parent c4df543b3d
commit ac71765c44
14 changed files with 312 additions and 224 deletions

View file

@ -2,6 +2,7 @@ package com.pixelized.rplexicon
import android.app.Activity
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
@ -30,13 +31,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import com.pixelized.rplexicon.ui.composable.BlurredOverlayHost
import com.pixelized.rplexicon.ui.composable.error.HandleFetchError
import com.pixelized.rplexicon.ui.navigation.ScreenNavHost
import com.pixelized.rplexicon.ui.screens.rolls.BlurredRollOverlayHostState
import com.pixelized.rplexicon.ui.screens.rolls.RollOverlay
import com.pixelized.rplexicon.ui.screens.rolls.BlurredOverlayHostState
import com.pixelized.rplexicon.ui.screens.rolls.BlurredRollOverlayHost
import com.pixelized.rplexicon.ui.screens.rolls.RollOverlayViewModel
import com.pixelized.rplexicon.ui.screens.rolls.rememberBlurredRollOverlayHostState
import com.pixelized.rplexicon.ui.screens.rolls.rememberBlurredOverlayHostState
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.modifier.doubleBorder
import dagger.hilt.android.AndroidEntryPoint
@ -49,7 +49,7 @@ val LocalActivity = compositionLocalOf<Activity> {
val LocalSnack = compositionLocalOf<SnackbarHostState> {
error("SnackbarHostState not available")
}
val LocalRollOverlay = compositionLocalOf<BlurredRollOverlayHostState> {
val LocalRollOverlay = compositionLocalOf<BlurredOverlayHostState> {
error("LocalRollOverlay not yet ready")
}
@ -73,8 +73,15 @@ class MainActivity : ComponentActivity() {
setContent {
val snack = remember { SnackbarHostState() }
val overlay = rememberBlurredRollOverlayHostState(
viewModel = rollViewModel,
val overlay = rememberBlurredOverlayHostState(
onPrepareRoll = { dice ->
dice?.let {
rollViewModel.prepareRoll(diceThrow = it)
}
},
onShowOverlay = { data ->
rollViewModel.setRollOverlayData(data)
}
)
CompositionLocalProvider(
@ -94,13 +101,9 @@ class MainActivity : ComponentActivity() {
.padding(paddingValues = padding),
color = MaterialTheme.colorScheme.background
) {
BlurredOverlayHost(
rollOverlayState = overlay,
overlay = {
RollOverlay(
viewModel = rollViewModel,
)
},
BlurredRollOverlayHost(
rollViewModel = rollViewModel,
state = overlay,
content = {
ScreenNavHost()
},
@ -150,7 +153,7 @@ class MainActivity : ComponentActivity() {
BackHandler(
enabled = overlay.isOverlayVisible,
onBack = { overlay.hideOverlay() },
onBack = { overlay.dismiss() },
)
HandleFetchError(

View file

@ -11,7 +11,7 @@ data class Item(
val context: String?,
val isContainer: Boolean,
val effect: Throw?,
val usable: Boolean,
val consumable: Boolean,
val icon: Uri?,
) {
val fullName: String = prefix?.let { "$it${name}" } ?: name

View file

@ -31,7 +31,7 @@ class ItemLexiconParser @Inject constructor(
type = row.parse(column = TYPE),
context = row.parse(column = CONTEXT),
isContainer = row.parseBool(column = CONTAINER) ?: false,
usable = row.parseBool(column = USABLE) ?: false,
consumable = row.parseBool(column = CONSUMABLE) ?: false,
effect = throwParser.parse(value = row.parse(column = EFFECT)),
icon = row.parseUri(column = ICON),
)
@ -51,11 +51,11 @@ class ItemLexiconParser @Inject constructor(
private val TYPE = column("Type")
private val CONTEXT = column("Contexte")
private val CONTAINER = column("Contenant")
private val USABLE = column("Utilisable")
private val CONSUMABLE = column("Consommable")
private val EFFECT = column("Effet")
private val ICON = column("Icone")
private val COLUMNS =
listOf(ID, PREFIX, NAME, TYPE, CONTEXT, CONTAINER, USABLE, EFFECT, ICON)
listOf(ID, PREFIX, NAME, TYPE, CONTEXT, CONTAINER, CONSUMABLE, EFFECT, ICON)
}
}

View file

@ -1,101 +0,0 @@
package com.pixelized.rplexicon.ui.composable
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.BlurredEdgeTreatment
import androidx.compose.ui.draw.blur
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.utilitary.extentions.lexicon
import com.pixelized.rplexicon.utilitary.extentions.modifier.clickableInterceptor
@Stable
interface BlurredOverlayHostState {
val isOverlayVisible: Boolean
fun showOverlay()
fun hideOverlay()
}
@Stable
private class BlurredOverlayHostStateImpl(
rollOverlayVisibilityState: MutableState<Boolean>,
) : BlurredOverlayHostState {
override var isOverlayVisible by rollOverlayVisibilityState
override fun showOverlay() {
isOverlayVisible = true
}
override fun hideOverlay() {
isOverlayVisible = false
}
}
@Composable
@Stable
fun rememberBlurredOverlayHostState(): BlurredOverlayHostState {
val rollOverlayVisibilityState = rememberSaveable { mutableStateOf(false) }
return remember {
BlurredOverlayHostStateImpl(
rollOverlayVisibilityState = rollOverlayVisibilityState
)
}
}
@Composable
fun BlurredOverlayHost(
rollOverlayState: BlurredOverlayHostState = rememberBlurredOverlayHostState(),
overlay: @Composable () -> Unit,
content: @Composable () -> Unit,
) {
val density = LocalDensity.current
Surface {
val blurs = animateDpAsState(
targetValue = if (rollOverlayState.isOverlayVisible) 4.dp else 0.dp,
label = "RollOverlayHostBlurAnimation",
)
Box(
modifier = Modifier.blur(
radius = blurs.value,
edgeTreatment = BlurredEdgeTreatment.Unbounded,
),
content = { content() },
)
AnimatedVisibility(
visible = rollOverlayState.isOverlayVisible,
enter = fadeIn() + slideInVertically { with(density) { 64.dp.roundToPx() } },
exit = fadeOut() + slideOutVertically { with(density) { 64.dp.roundToPx() } },
content = {
Box(
modifier = Modifier
.clickableInterceptor()
.background(color = MaterialTheme.lexicon.colorScheme.rollOverlayBrush)
.fillMaxSize()
.systemBarsPadding(),
) {
overlay()
}
},
)
}
}

View file

@ -59,6 +59,7 @@ import com.pixelized.rplexicon.NO_WINDOW_INSETS
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.composable.KeepOnScreen
import com.pixelized.rplexicon.ui.composable.Loader
import com.pixelized.rplexicon.ui.screens.rolls.RollResult
import com.pixelized.rplexicon.ui.composable.Toolbar
import com.pixelized.rplexicon.ui.composable.edit.HandleHitPointEditDialog
import com.pixelized.rplexicon.ui.composable.edit.HandleSkillEditDialog
@ -83,9 +84,9 @@ import com.pixelized.rplexicon.ui.screens.character.pages.actions.SpellsViewMode
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationPage
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationPagePreview
import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationViewModel
import com.pixelized.rplexicon.ui.screens.character.pages.inventory_OLD.InventoryViewModelOLD
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.InventoryPage2
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.InventoryPreview
import com.pixelized.rplexicon.ui.screens.character.pages.inventory_OLD.InventoryViewModelOLD
import com.pixelized.rplexicon.ui.screens.character.pages.proficiency.ProficiencyPage
import com.pixelized.rplexicon.ui.screens.character.pages.proficiency.ProficiencyPreview
import com.pixelized.rplexicon.ui.screens.character.pages.proficiency.ProficiencyViewModel
@ -149,8 +150,7 @@ fun CharacterSheetScreen(
},
onInitiative = {
scope.launch {
overlay.prepareRoll(diceThrow = headerViewModel.initiativeRoll())
overlay.showOverlay()
overlay.showOverlay(diceThrow = headerViewModel.initiativeRoll())
}
},
onHitPoint = headerViewModel::toggleHitPointDialog,
@ -159,8 +159,16 @@ fun CharacterSheetScreen(
},
onDeathRoll = {
scope.launch {
overlay.prepareRoll(diceThrow = headerViewModel.onDeathThrow())
overlay.showOverlay()
val dices = headerViewModel.onDeathThrow()
val result = overlay.showOverlay(diceThrow = dices)
if (result is RollResult.Roll) {
when {
result.isCriticalSuccess -> headerViewModel.onDeathSuccess(critical = true)
result.isCriticalFailure -> headerViewModel.onDeathFailure(critical = true)
result.value < 10 -> headerViewModel.onDeathFailure()
else -> headerViewModel.onDeathSuccess()
}
}
}
},
onDeathSuccess = headerViewModel::onDeathSuccess,
@ -203,13 +211,12 @@ fun CharacterSheetScreen(
onLevel = { spell, level ->
scope.launch {
sheetState.hide()
overlay.prepareRoll(
overlay.showOverlay(
diceThrow = spellsViewModel.onCastSpell(
spell,
level
)
)
overlay.showOverlay()
}
},
)
@ -237,7 +244,7 @@ fun CharacterSheetScreen(
}
BackHandler(enabled = overlay.isOverlayVisible) {
overlay.hideOverlay()
overlay.dismiss()
}
KeepOnScreen()

View file

@ -63,16 +63,14 @@ fun ActionPage(
onAttackHit = { id ->
attacksViewModel.onHitRoll(id)?.let {
scope.launch {
overlay.prepareRoll(diceThrow = it)
overlay.showOverlay()
overlay.showOverlay(diceThrow = it)
}
}
},
onAttackDamage = { id ->
attacksViewModel.onDamageRoll(id)?.let {
scope.launch {
overlay.prepareRoll(diceThrow = it)
overlay.showOverlay()
overlay.showOverlay(diceThrow = it)
}
}
},
@ -81,8 +79,7 @@ fun ActionPage(
},
onSkillThrow = {
scope.launch {
overlay.prepareRoll(diceThrow = skillViewModel.onSkillRoll(it.label))
overlay.showOverlay()
overlay.showOverlay(diceThrow = skillViewModel.onSkillRoll(it.label))
}
},
onSkillInfo = {
@ -100,14 +97,12 @@ fun ActionPage(
},
onSpellHit = { id ->
scope.launch {
overlay.prepareRoll(diceThrow = spellsViewModel.onSpellHitRoll(id))
overlay.showOverlay()
overlay.showOverlay(diceThrow = spellsViewModel.onSpellHitRoll(id))
}
},
onSpellDamage = { id ->
scope.launch {
overlay.prepareRoll(diceThrow = spellsViewModel.onSpellDamageRoll(id))
overlay.showOverlay()
overlay.showOverlay(diceThrow = spellsViewModel.onSpellDamageRoll(id))
}
},
onCast = {
@ -116,8 +111,7 @@ fun ActionPage(
scope.launch { sheetState.show() }
} else {
scope.launch {
overlay.prepareRoll(diceThrow = spellsViewModel.onCastSpell(it))
overlay.showOverlay()
overlay.showOverlay(diceThrow = spellsViewModel.onCastSpell(it))
}
}
},

View file

@ -113,19 +113,25 @@ class HeaderViewModel @Inject constructor(
return DiceThrow.DeathSavingThrow(character = character)
}
fun onDeathSuccess() {
fun onDeathSuccess(
critical: Boolean = false,
) {
val token = if (critical) 2 else 1
firebaseRepository.setCharacterDeathCounter(
character = character,
success = ((fireData.value?.deathSuccess ?: 0) + 1) % 4,
success = ((fireData.value?.deathSuccess ?: 0) + token) % 4,
failure = fireData.value?.deathFailure ?: 0,
)
}
fun onDeathFailure() {
fun onDeathFailure(
critical: Boolean = false,
) {
val token = if (critical) 2 else 1
firebaseRepository.setCharacterDeathCounter(
character = character,
success = fireData.value?.deathSuccess ?: 0,
failure = ((fireData.value?.deathFailure ?: 0) + 1) % 4,
failure = ((fireData.value?.deathFailure ?: 0) + token) % 4,
)
}

View file

@ -57,6 +57,7 @@ import com.pixelized.rplexicon.ui.screens.character.pages.inventory.item_detail.
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.item_detail.ItemDetailViewModel
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.item_list.ItemListDialog
import com.pixelized.rplexicon.ui.screens.character.pages.inventory.item_list.ItemListViewModel
import com.pixelized.rplexicon.ui.screens.rolls.RollResult
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import kotlinx.coroutines.launch
@ -107,11 +108,14 @@ fun InventoryPage2(
},
onItemUse = { id, quantity ->
// try to roll the dices.
val diceThrow = inventoryViewModel.useItem(itemId = id)
if (diceThrow != null) {
scope.launch {
overlay.prepareRoll(diceThrow = diceThrow)
overlay.showOverlay()
scope.launch {
val (item, dices) = inventoryViewModel.useItem(itemId = id)
val result = overlay.showOverlay(diceThrow = dices)
if (result is RollResult.Roll && item?.consumable == true) {
inventoryViewModel.setItemQuantity(
itemId = id,
quantity = quantity - 1
)
}
}
},
@ -141,10 +145,14 @@ fun InventoryPage2(
// hide the detail dialog
itemDetailViewModel.hide()
// try to roll the dices.
inventoryViewModel.useItem(itemId = detail.id)?.let { diceThrow ->
scope.launch {
overlay.prepareRoll(diceThrow = diceThrow)
overlay.showOverlay()
scope.launch {
val (item, dices) = inventoryViewModel.useItem(itemId = detail.id)
val result = overlay.showOverlay(diceThrow = dices)
if (result is RollResult.Roll && item?.consumable == true) {
inventoryViewModel.setItemQuantity(
itemId = detail.id,
quantity = detail.quantity?.minus(1) ?: 0
)
}
}
}

View file

@ -9,6 +9,7 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.data.model.DiceThrow
import com.pixelized.rplexicon.data.model.item.Item
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
import com.pixelized.rplexicon.data.repository.character.ItemsRepository
import com.pixelized.rplexicon.data.repository.firebase.inventory.InventoryFireRepository
@ -329,16 +330,18 @@ class InventoryViewModel @Inject constructor(
fun useItem(
itemId: String,
): DiceThrow? {
): Pair<Item?, DiceThrow?> {
val item = itemRepository.find(id = itemId)
return if (item?.effect != null) {
DiceThrow.Object(
character = character,
itemId = item.id,
itemName = item.name,
)
return if (item == null) {
null to null
} else {
null
item to item.effect?.let {
DiceThrow.Object(
character = character,
itemId = item.id,
itemName = item.name,
)
}
}
}
}

View file

@ -112,8 +112,7 @@ fun InventoryPageOLD(
// detailViewModel.hide(detail)
viewModel.onUse(itemId = detail.id)?.let { diceThrow ->
scope.launch {
overlay.prepareRoll(diceThrow = diceThrow)
overlay.showOverlay()
overlay.showOverlay(diceThrow = diceThrow)
}
}
}

View file

@ -83,14 +83,12 @@ fun ProficiencyPage(
passives = viewModel.skills,
onStats = { stat ->
scope.launch {
overlay.prepareRoll(diceThrow = viewModel.statRoll(stat.id))
overlay.showOverlay()
overlay.showOverlay(diceThrow = viewModel.statRoll(stat.id))
}
},
onProficiencies = { proficiency ->
scope.launch {
overlay.prepareRoll(diceThrow = viewModel.proficiencyRoll(proficiency.id))
overlay.showOverlay()
overlay.showOverlay(diceThrow = viewModel.proficiencyRoll(proficiency.id))
}
},
onSkillCount = {
@ -98,8 +96,7 @@ fun ProficiencyPage(
},
onSkillThrow = {
scope.launch {
overlay.prepareRoll(diceThrow = viewModel.onSkillRoll(it.label))
overlay.showOverlay()
overlay.showOverlay(diceThrow = viewModel.onSkillRoll(it.label))
}
},
onSkillInfo = {

View file

@ -0,0 +1,216 @@
package com.pixelized.rplexicon.ui.screens.rolls
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.BlurredEdgeTreatment
import androidx.compose.ui.draw.blur
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.data.model.DiceThrow
import com.pixelized.rplexicon.utilitary.extentions.lexicon
import com.pixelized.rplexicon.utilitary.extentions.modifier.clickableInterceptor
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.coroutines.resume
@Stable
interface BlurredOverlayHostState {
val isOverlayVisible: Boolean
val overlayData: BlurredOverlayData?
suspend fun showOverlay(diceThrow: DiceThrow?): RollResult
fun dismiss()
}
@Stable
interface BlurredOverlayData {
val diceThrow: DiceThrow
fun setRollValue(
value: Int,
isCriticalSuccess: Boolean,
isCriticalFailure: Boolean,
)
fun dismiss()
}
@Stable
sealed class RollResult {
data class Roll(
val value: Int,
val isCriticalSuccess: Boolean,
val isCriticalFailure: Boolean,
) : RollResult()
data object Dismissed : RollResult()
data object Void : RollResult()
}
@Stable
private class BlurredOverlayHostStateImpl(
rollOverlayVisibilityState: MutableState<Boolean>,
private val onPrepareRoll: State<suspend (DiceThrow?) -> Unit>,
private val onShowOverlay: State<(BlurredOverlayData) -> Unit>,
) : BlurredOverlayHostState {
private val mutex = Mutex()
private var _currentBlurredOverlayData: BlurredOverlayDataImpl? by mutableStateOf(null)
override val overlayData: BlurredOverlayData? get() = _currentBlurredOverlayData
override var isOverlayVisible by rollOverlayVisibilityState
override suspend fun showOverlay(
diceThrow: DiceThrow?,
): RollResult {
return if (diceThrow == null) {
RollResult.Void
} else {
mutex.withLock {
try {
onPrepareRoll.value.invoke(diceThrow)
isOverlayVisible = true
return suspendCancellableCoroutine { continuation ->
val data = BlurredOverlayDataImpl(
continuation = continuation,
diceThrow = diceThrow
)
_currentBlurredOverlayData = data
onShowOverlay.value.invoke(data)
}
} finally {
_currentBlurredOverlayData = null
}
}
}
}
override fun dismiss() {
isOverlayVisible = false
_currentBlurredOverlayData?.dismiss()
}
}
private class BlurredOverlayDataImpl(
private val continuation: CancellableContinuation<RollResult>,
override val diceThrow: DiceThrow,
) : BlurredOverlayData {
private var result: RollResult = RollResult.Dismissed
override fun setRollValue(
value: Int,
isCriticalSuccess: Boolean,
isCriticalFailure: Boolean,
) {
if (continuation.isActive) {
result = RollResult.Roll(
value = value,
isCriticalSuccess = isCriticalSuccess,
isCriticalFailure = isCriticalFailure,
)
}
}
override fun dismiss() {
if (continuation.isActive) {
continuation.resume(result)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as BlurredOverlayDataImpl
return continuation == other.continuation
}
override fun hashCode(): Int {
return continuation.hashCode()
}
}
@Composable
@Stable
fun rememberBlurredOverlayHostState(
onPrepareRoll: suspend (DiceThrow?) -> Unit,
onShowOverlay: (BlurredOverlayData) -> Unit,
): BlurredOverlayHostState {
val rollOverlayVisibilityState = rememberSaveable { mutableStateOf(false) }
val currentOnPrepareRoll = rememberUpdatedState(newValue = onPrepareRoll)
val currentOnShowOverlay = rememberUpdatedState(newValue = onShowOverlay)
return remember(currentOnShowOverlay, rollOverlayVisibilityState) {
BlurredOverlayHostStateImpl(
rollOverlayVisibilityState = rollOverlayVisibilityState,
onPrepareRoll = currentOnPrepareRoll,
onShowOverlay = currentOnShowOverlay,
)
}
}
@Composable
fun BlurredRollOverlayHost(
rollViewModel: RollOverlayViewModel,
state: BlurredOverlayHostState,
content: @Composable () -> Unit,
) {
val density = LocalDensity.current
Surface {
val blurs = animateDpAsState(
targetValue = if (state.isOverlayVisible) 4.dp else 0.dp,
label = "RollOverlayHostBlurAnimation",
)
Box(
modifier = Modifier.blur(
radius = blurs.value,
edgeTreatment = BlurredEdgeTreatment.Unbounded,
),
content = { content() },
)
AnimatedVisibility(
visible = state.isOverlayVisible,
enter = fadeIn() + slideInVertically { with(density) { 64.dp.roundToPx() } },
exit = fadeOut() + slideOutVertically { with(density) { 64.dp.roundToPx() } },
content = {
Box(
modifier = Modifier
.clickableInterceptor()
.background(color = MaterialTheme.lexicon.colorScheme.rollOverlayBrush)
.fillMaxSize()
.systemBarsPadding(),
) {
RollOverlay(
viewModel = rollViewModel,
)
}
},
)
}
}

View file

@ -5,16 +5,9 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.activity.compose.BackHandler
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
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
@ -42,7 +35,6 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
@ -50,13 +42,10 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
@ -65,7 +54,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
@ -73,9 +61,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.LocalRollOverlay
import com.pixelized.rplexicon.NO_WINDOW_INSETS
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.DiceThrow
import com.pixelized.rplexicon.isInDarkTheme
import com.pixelized.rplexicon.ui.composable.BlurredOverlayHostState
import com.pixelized.rplexicon.ui.composable.CategoryHeader
import com.pixelized.rplexicon.ui.composable.ModalNavigationDrawer
import com.pixelized.rplexicon.ui.composable.Toolbar
@ -87,7 +73,6 @@ import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCard
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio
import com.pixelized.rplexicon.ui.screens.rolls.composable.rememberSlideInOutAnimation
import com.pixelized.rplexicon.ui.screens.rolls.composable.slideInOutAnimation
import com.pixelized.rplexicon.ui.screens.rolls.preview.rememberRollAlterations
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.lexicon
@ -118,7 +103,7 @@ fun RollOverlay(
scope.launch { drawer.close() }
},
onClose = {
overlay.hideOverlay()
overlay.dismiss()
},
onDice = {
viewModel.roll(uiScope = scope)
@ -466,43 +451,4 @@ private class RollOverlayPreviewProvider : PreviewParameterProvider<RollOverlayP
card = mutableStateOf(null),
),
)
}
@Stable
interface BlurredRollOverlayHostState : BlurredOverlayHostState {
suspend fun prepareRoll(diceThrow: DiceThrow)
}
@Stable
private class BlurredRollOverlayHostStateImpl(
private val viewModel: RollOverlayViewModel,
rollOverlayVisibilityState: MutableState<Boolean>,
) : BlurredRollOverlayHostState {
override var isOverlayVisible by rollOverlayVisibilityState
override suspend fun prepareRoll(diceThrow: DiceThrow) {
viewModel.prepareRoll(diceThrow)
}
override fun showOverlay() {
isOverlayVisible = true
}
override fun hideOverlay() {
isOverlayVisible = false
}
}
@Composable
@Stable
fun rememberBlurredRollOverlayHostState(
viewModel: RollOverlayViewModel,
): BlurredRollOverlayHostState {
val rollOverlayVisibilityState = rememberSaveable { mutableStateOf(false) }
return remember {
BlurredRollOverlayHostStateImpl(
viewModel = viewModel,
rollOverlayVisibilityState = rollOverlayVisibilityState
)
}
}

View file

@ -62,6 +62,7 @@ class RollOverlayViewModel @Inject constructor(
activeAlterationRepository: ActiveAlterationRepository,
application: Application,
) : AndroidViewModel(application) {
private var _overlay: BlurredOverlayData? = null
private val _diceThrow = MutableStateFlow<DiceThrow?>(null)
private val _alterationsOverride = MutableStateFlow<Map<String, Boolean>>(emptyMap())
@ -126,6 +127,10 @@ class RollOverlayViewModel @Inject constructor(
private val _alterationDetailDialog = mutableStateOf<AlterationDialogDetailUio?>(null)
val alterationDetailDialog: State<AlterationDialogDetailUio?> get() = _alterationDetailDialog
fun setRollOverlayData(data: BlurredOverlayData) {
_overlay = data
}
suspend fun prepareRoll(diceThrow: DiceThrow) {
// save the dice throw.
_diceThrow.value = diceThrow
@ -184,6 +189,11 @@ class RollOverlayViewModel @Inject constructor(
delay(RollDiceUio.ROLL_DURATION.toLong())
// display the roll result & share with other player
if (isActive) {
_overlay?.setRollValue(
value = result.throws.result.toIntOrNull() ?: 0,
isCriticalSuccess = result.throws.isCriticalSuccess ?: false,
isCriticalFailure = result.throws.isCriticalFailure ?: false,
)
_dice.value = result.dice
// share the result
withContext(Dispatchers.IO) {