Change the roll bahavior (state to shared) after levelup.
This commit is contained in:
parent
03dbd7aad6
commit
db98fbede7
5 changed files with 84 additions and 52 deletions
|
|
@ -2,6 +2,7 @@ package com.pixelized.desktop.lwa.repository.resources
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.graphics.toComposeImageBitmap
|
import androidx.compose.ui.graphics.toComposeImageBitmap
|
||||||
|
import com.pixelized.desktop.lwa.ui.composable.image.ImagerModelConverter
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
import io.ktor.client.statement.readRawBytes
|
import io.ktor.client.statement.readRawBytes
|
||||||
|
|
@ -9,15 +10,17 @@ import org.jetbrains.skia.Image
|
||||||
|
|
||||||
class ImageResourcesRepository(
|
class ImageResourcesRepository(
|
||||||
private val httpClient: HttpClient,
|
private val httpClient: HttpClient,
|
||||||
|
private val googleImageConverter: ImagerModelConverter,
|
||||||
) {
|
) {
|
||||||
suspend fun load(url: String): ImageBitmap {
|
suspend fun load(url: String): ImageBitmap {
|
||||||
try {
|
try {
|
||||||
val byteArray = httpClient.get(url).readRawBytes()
|
val unwrapUri = googleImageConverter.unwrap(model = url)
|
||||||
|
val byteArray = httpClient.get(unwrapUri).readRawBytes()
|
||||||
val skiaImage = Image.makeFromEncoded(byteArray)
|
val skiaImage = Image.makeFromEncoded(byteArray)
|
||||||
return skiaImage.toComposeImageBitmap()
|
return skiaImage.toComposeImageBitmap()
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
// TODO proper exception handling (error bus ?)
|
// TODO proper exception handling (error bus ?)
|
||||||
return ImageBitmap(width = 0, height = 0)
|
return ImageBitmap(width = 1, height = 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,11 +9,17 @@ class ImagerModelConverter {
|
||||||
model: Any?,
|
model: Any?,
|
||||||
): Any? {
|
): Any? {
|
||||||
return when (model) {
|
return when (model) {
|
||||||
is String -> googleDriveUrlRegex.find(model)?.let {
|
is String -> unwrap(model = model)
|
||||||
val id = it.groupValues.getOrNull(1)
|
|
||||||
"$workingGoogleDriveUri$id"
|
|
||||||
} ?: model
|
|
||||||
else -> model
|
else -> model
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun unwrap(
|
||||||
|
model: String,
|
||||||
|
): String {
|
||||||
|
return googleDriveUrlRegex.find(model)?.let {
|
||||||
|
val id = it.groupValues.getOrNull(1)
|
||||||
|
"$workingGoogleDriveUri$id"
|
||||||
|
} ?: model
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -102,7 +102,7 @@ fun CharacterRibbon(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
CharacterRibbonRoll(
|
CharacterRibbonRoll(
|
||||||
value = viewModel.roll(characterSheetId = it.characterSheetId).value,
|
roll = viewModel.roll(characterSheetId = it.characterSheetId),
|
||||||
)
|
)
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = it.levelUp,
|
visible = it.levelUp,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
|
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
|
||||||
|
|
||||||
import androidx.compose.material.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||||
|
|
@ -18,17 +11,18 @@ import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
|
||||||
import com.pixelized.desktop.lwa.repository.settings.model.Settings
|
import com.pixelized.desktop.lwa.repository.settings.model.Settings
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRollUio
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonRollUio
|
||||||
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonUio
|
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.common.CharacterRibbonUio
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
|
||||||
import com.pixelized.shared.lwa.model.campaign.Campaign
|
import com.pixelized.shared.lwa.model.campaign.Campaign
|
||||||
import com.pixelized.shared.lwa.protocol.websocket.RollEvent.Critical
|
import com.pixelized.shared.lwa.protocol.websocket.RollEvent.Critical
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.shareIn
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
|
|
||||||
|
|
@ -41,7 +35,7 @@ abstract class CharacterRibbonViewModel(
|
||||||
private val ribbonFactory: CharacterRibbonFactory,
|
private val ribbonFactory: CharacterRibbonFactory,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val rolls = hashMapOf<String, MutableState<CharacterRibbonRollUio?>>()
|
private val rollFlowCache = hashMapOf<String, SharedFlow<CharacterRibbonRollUio?>>()
|
||||||
|
|
||||||
abstract fun fetch(
|
abstract fun fetch(
|
||||||
campaign: Campaign,
|
campaign: Campaign,
|
||||||
|
|
@ -119,40 +113,35 @@ abstract class CharacterRibbonViewModel(
|
||||||
.getOrNull(index)
|
.getOrNull(index)
|
||||||
?.characterSheetId
|
?.characterSheetId
|
||||||
|
|
||||||
@Composable
|
|
||||||
@Stable
|
@Stable
|
||||||
fun roll(
|
fun roll(
|
||||||
characterSheetId: String,
|
characterSheetId: String,
|
||||||
): State<CharacterRibbonRollUio?> {
|
): SharedFlow<CharacterRibbonRollUio?> {
|
||||||
val colorScheme = MaterialTheme.lwa.colorScheme
|
return rollFlowCache.getOrPut(key = characterSheetId) {
|
||||||
val state = remember(characterSheetId) {
|
|
||||||
rolls.getOrPut(characterSheetId) { mutableStateOf(null) }
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(characterSheetId) {
|
|
||||||
combine(
|
combine(
|
||||||
settingsRepository.settingsFlow(),
|
settingsRepository.settingsFlow(),
|
||||||
rollHistoryRepository.rolls(),
|
rollHistoryRepository.rolls().filter { it.characterSheetId == characterSheetId },
|
||||||
) { settings, roll ->
|
) { settings, roll ->
|
||||||
if (settings.portrait.dynamicDice && characterSheetId == roll.characterSheetId) {
|
if (settings.portrait.dynamicDice.not()) return@combine null
|
||||||
state.value = CharacterRibbonRollUio(
|
|
||||||
rollId = roll.uuid,
|
|
||||||
hideDelay = settings.portrait.dynamicDiceDelay,
|
|
||||||
characterSheetId = characterSheetId,
|
|
||||||
value = roll.rollValue,
|
|
||||||
tint = when (roll.critical) {
|
|
||||||
Critical.CRITICAL_SUCCESS -> colorScheme.portrait.criticalSuccess
|
|
||||||
Critical.SPECIAL_SUCCESS -> colorScheme.portrait.spacialSuccess
|
|
||||||
Critical.SUCCESS -> colorScheme.portrait.success
|
|
||||||
Critical.FAILURE -> colorScheme.portrait.failure
|
|
||||||
Critical.CRITICAL_FAILURE -> colorScheme.portrait.criticalFailure
|
|
||||||
null -> colorScheme.portrait.default
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.launchIn(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
return state
|
CharacterRibbonRollUio(
|
||||||
|
rollId = roll.uuid,
|
||||||
|
hideDelay = settings.portrait.dynamicDiceDelay,
|
||||||
|
characterSheetId = characterSheetId,
|
||||||
|
value = roll.rollValue,
|
||||||
|
critical = when (roll.critical) {
|
||||||
|
Critical.CRITICAL_SUCCESS -> CharacterRibbonRollUio.Critical.CRITICAL_SUCCESS
|
||||||
|
Critical.SPECIAL_SUCCESS -> CharacterRibbonRollUio.Critical.SPECIAL_SUCCESS
|
||||||
|
Critical.SUCCESS -> CharacterRibbonRollUio.Critical.SUCCESS
|
||||||
|
Critical.FAILURE -> CharacterRibbonRollUio.Critical.FAILURE
|
||||||
|
Critical.CRITICAL_FAILURE -> CharacterRibbonRollUio.Critical.CRITICAL_FAILURE
|
||||||
|
else -> null
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}.shareIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -23,16 +23,19 @@ import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.saveable.SaverScope
|
import androidx.compose.runtime.saveable.SaverScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.pixelized.desktop.lwa.ui.theme.color.LwaColors
|
||||||
import com.pixelized.desktop.lwa.ui.theme.lwa
|
import com.pixelized.desktop.lwa.ui.theme.lwa
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
import lwacharactersheet.composeapp.generated.resources.ic_d20_24dp
|
import lwacharactersheet.composeapp.generated.resources.ic_d20_24dp
|
||||||
|
|
@ -45,8 +48,17 @@ data class CharacterRibbonRollUio(
|
||||||
val hideDelay: Int,
|
val hideDelay: Int,
|
||||||
val characterSheetId: String,
|
val characterSheetId: String,
|
||||||
val value: Int?,
|
val value: Int?,
|
||||||
val tint: Color?,
|
val critical: Critical?,
|
||||||
)
|
) {
|
||||||
|
@Stable
|
||||||
|
enum class Critical {
|
||||||
|
CRITICAL_SUCCESS,
|
||||||
|
SPECIAL_SUCCESS,
|
||||||
|
SUCCESS,
|
||||||
|
FAILURE,
|
||||||
|
CRITICAL_FAILURE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class CharacterRibbonRollAnimation(
|
class CharacterRibbonRollAnimation(
|
||||||
|
|
@ -87,14 +99,30 @@ class CharacterRibbonRollAnimation(
|
||||||
fun CharacterRibbonRoll(
|
fun CharacterRibbonRoll(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
size: DpSize = MaterialTheme.lwa.dimen.portrait.minimized,
|
size: DpSize = MaterialTheme.lwa.dimen.portrait.minimized,
|
||||||
value: CharacterRibbonRollUio?,
|
roll: Flow<CharacterRibbonRollUio?>,
|
||||||
|
) {
|
||||||
|
val rollValue = roll.collectAsStateWithLifecycle(initialValue = null)
|
||||||
|
|
||||||
|
CharacterRibbonRoll(
|
||||||
|
modifier = modifier,
|
||||||
|
size = size,
|
||||||
|
roll = rollValue,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CharacterRibbonRoll(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
size: DpSize = MaterialTheme.lwa.dimen.portrait.minimized,
|
||||||
|
portrait: LwaColors.Portrait = MaterialTheme.lwa.colorScheme.portrait,
|
||||||
|
roll: State<CharacterRibbonRollUio?>,
|
||||||
) {
|
) {
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.width(width = size.width)
|
.width(width = size.width)
|
||||||
.aspectRatio(ratio = 1f)
|
.aspectRatio(ratio = 1f)
|
||||||
.graphicsLayer { clip = false },
|
.graphicsLayer { clip = false },
|
||||||
targetState = value,
|
targetState = roll.value,
|
||||||
transitionSpec = {
|
transitionSpec = {
|
||||||
val enter = fadeIn()
|
val enter = fadeIn()
|
||||||
val exit = fadeOut()
|
val exit = fadeOut()
|
||||||
|
|
@ -106,9 +134,15 @@ fun CharacterRibbonRoll(
|
||||||
rollDelay = it?.hideDelay ?: 1000,
|
rollDelay = it?.hideDelay ?: 1000,
|
||||||
)
|
)
|
||||||
val color = animateColorAsState(
|
val color = animateColorAsState(
|
||||||
targetValue = it?.tint ?: Color.Transparent,
|
targetValue = when (it?.critical) {
|
||||||
|
CharacterRibbonRollUio.Critical.CRITICAL_SUCCESS -> portrait.criticalSuccess
|
||||||
|
CharacterRibbonRollUio.Critical.SPECIAL_SUCCESS -> portrait.spacialSuccess
|
||||||
|
CharacterRibbonRollUio.Critical.SUCCESS -> portrait.success
|
||||||
|
CharacterRibbonRollUio.Critical.FAILURE -> portrait.failure
|
||||||
|
CharacterRibbonRollUio.Critical.CRITICAL_FAILURE -> portrait.criticalFailure
|
||||||
|
null -> portrait.default
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.graphicsLayer {
|
modifier = Modifier.graphicsLayer {
|
||||||
this.alpha = animation.animatedAlpha.value
|
this.alpha = animation.animatedAlpha.value
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue