Add the "know what you roll" feature, allow automation after a roll.
This commit is contained in:
parent
c4df543b3d
commit
ac71765c44
14 changed files with 312 additions and 224 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue