Add roll history page

This commit is contained in:
Thomas Andres Gomez 2024-11-10 17:26:58 +01:00
parent f92922c228
commit ca456b55d9
20 changed files with 488 additions and 133 deletions

View file

@ -1,7 +1,9 @@
plugins {
// this is necessary to avoid the plugins to be loaded multiple times
// in each subproject's classloader
alias(libs.plugins.kotlinMultiplatform) apply false
alias(libs.plugins.kotlinSerialization) apply false
// alias(libs.plugins.kotlinKtor) apply false
alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.composeCompiler) apply false
alias(libs.plugins.kotlinMultiplatform) apply false
}

View file

@ -3,10 +3,9 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
// alias(libs.plugins.kotlinKtor)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
// kotlin("jvm") version "1.9.20"
// alias(libs.plugins.kotlinKtor)
}
kotlin {

View file

@ -1,14 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="main_page__create_action">Créer une feuille de personnage</string>
<string name="main_page__network_action">Configuration réseau</string>
<string name="main_page__roll_history_action">Consulter l'historique des lancés</string>
<string name="main_page__network_action">Configuration de la table</string>
<string name="main_page__roll_history_action">Consulter l'historique des lancers</string>
<string name="roll_page__roll__label">Jet de :</string>
<string name="roll_page__roll__success_label">Réussite si lancer inférieur ou égale à : %1$s</string>
<string name="roll_page__critical_success">Réussite critique</string>
<string name="roll_page__special_success">Réussite spéciale</string>
<string name="roll_page__success">Réussite</string>
<string name="roll_page__failure">Échec</string>
<string name="roll_page__critical_failure">Échec critique</string>
<string name="roll_page__dc__label">Dégré de difficulté :</string>
<string name="roll_page__dc_easy__label">Facile</string>
<string name="roll_page__dc_normal__label">Normal</string>
<string name="roll_page__dc_hard__label">Difficile</string>
<string name="roll_page__dc_impossible__label">Impossible</string>
<string name="character_sheet_edit__title">Création de personnage</string>
<string name="character_sheet_edit__name_placeholder">Nom</string>
@ -68,7 +75,7 @@
<string name="character_sheet__occupations_title">Occupations</string>
<string name="character_sheet__magics__title">Compétences magiques</string>
<string name="network__title">Configuration réseau</string>
<string name="network__title">Configuration de la table</string>
<string name="network__player_name__label">Nom du joueur</string>
<string name="network__host__label">host</string>
<string name="network__port__label">port</string>
@ -83,5 +90,7 @@
<string name="network__socket__type_client">Client</string>
<string name="network__socket__type_none">Aucun</string>
<string name="roll_history__title">Historique des lancés</string>
<string name="roll_history__title">Historique des lancers</string>
<string name="roll_history__item__throw">lance</string>
<string name="roll_history__item__difficulty">Difficulté</string>
</resources>

View file

@ -168,7 +168,10 @@ fun HandleCharacterSheetCreation(
title = stringResource(Res.string.character_sheet_edit__title),
) {
CharacterSheetMainNavHost(
startDestination = CharacterSheetEditDestination.navigationRoute(id = null)
startDestination = CharacterSheetEditDestination.navigationRoute(
id = null,
enableBack = false,
)
)
}
}

View file

@ -3,6 +3,7 @@ package com.pixelized.desktop.lwa.navigation.destination
import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEditPage
@ -11,22 +12,33 @@ import com.pixelized.desktop.lwa.utils.extention.ARG
object CharacterSheetEditDestination {
private const val ROUTE = "character.sheet.edit"
private const val CHARACTER_ID = "id"
private const val ENABLE_BACK = "enable_back"
fun baseRoute() = "$ROUTE?${CHARACTER_ID.ARG}"
fun baseRoute() = "$ROUTE?${CHARACTER_ID.ARG}&${ENABLE_BACK.ARG}"
fun navigationRoute(id: String?) = "$ROUTE?$CHARACTER_ID=$id"
fun navigationRoute(id: String?, enableBack: Boolean) = ROUTE +
"?$CHARACTER_ID=$id" +
"&$ENABLE_BACK=$enableBack"
fun arguments() = listOf(
navArgument(CHARACTER_ID) {
nullable = true
type = NavType.StringType
},
navArgument(ENABLE_BACK) {
nullable = false
type = NavType.BoolType
}
)
data class Argument(
val id: String?,
val enableBack: Boolean,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
id = savedStateHandle.get<String>(CHARACTER_ID)
id = savedStateHandle.get<String>(CHARACTER_ID),
enableBack = savedStateHandle.get<Boolean>(ENABLE_BACK)
?: error("Missing enableBack argument"),
)
}
}
@ -42,7 +54,8 @@ fun NavGraphBuilder.composableCharacterSheetEditPage() {
fun NavHostController.navigateToCharacterSheetEdit(
id: String? = null,
enableBack: Boolean = true,
) {
val route = CharacterSheetEditDestination.navigationRoute(id = id)
val route = CharacterSheetEditDestination.navigationRoute(id = id, enableBack = enableBack)
navigate(route = route)
}

View file

@ -27,6 +27,9 @@ typealias Server = EmbeddedServer<NettyApplicationEngine, NettyApplicationEngine
typealias Client = HttpClient
object NetworkRepository {
const val DEFAULT_PORT = 16030
const val DEFAULT_HOST = "pixelized.freeboxos.fr"
private val scope = CoroutineScope(Dispatchers.IO)
private var networkJob: Job? = null
private var server: Server? = null

View file

@ -15,7 +15,7 @@ import kotlin.time.Duration.Companion.seconds
// https://ktor.io/docs/server-websockets.html#handle-multiple-session
fun server(
port: Int = 8080,
port: Int,
handler: suspend DefaultWebSocketServerSession.() -> Unit
): EmbeddedServer<NettyApplicationEngine, NettyApplicationEngine.Configuration> {
return embeddedServer(

View file

@ -4,6 +4,9 @@ import kotlinx.serialization.Serializable
@Serializable
data class RollMessage(
val label: String,
val roll: Int,
): MessageContent
val skillLabel: String,
val resultLabel: String?,
val rollDifficulty: String?,
val rollValue: Int,
val rollSuccessLimit: Int?,
) : MessageContent

View file

@ -31,11 +31,20 @@ object RollHistoryRepository {
}
suspend fun share(
label: String,
roll: Int,
skillLabel: String,
rollDifficulty: String?,
rollValue: Int,
resultLabel: String?,
rollSuccessLimit: Int?,
) {
network.share(
content = RollMessage(label = label, roll = roll)
content = RollMessage(
skillLabel = skillLabel,
rollDifficulty = rollDifficulty,
rollValue = rollValue,
resultLabel = resultLabel,
rollSuccessLimit = rollSuccessLimit,
)
)
}
}

View file

@ -113,11 +113,11 @@ fun CharacterSheetPage(
CharacterSheetPageContent(
modifier = Modifier.fillMaxSize(),
characterSheet = sheet,
onBack = {
screen.popBackStack()
},
onEdit = {
screen.navigateToCharacterSheetEdit(id = sheet.id)
screen.navigateToCharacterSheetEdit(
id = sheet.id,
enableBack = true,
)
},
onDelete = {
scope.launch {
@ -154,7 +154,6 @@ fun CharacterSheetPageContent(
modifier: Modifier = Modifier,
scrollState: ScrollState = rememberScrollState(),
characterSheet: CharacterSheetPageUio,
onBack: () -> Unit,
onEdit: () -> Unit,
onDelete: () -> Unit,
onCharacteristic: (characteristic: CharacterSheetPageUio.Characteristic) -> Unit,
@ -178,6 +177,7 @@ fun CharacterSheetPageContent(
) {
Icon(
imageVector = Icons.Default.Edit,
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
}
@ -186,6 +186,7 @@ fun CharacterSheetPageContent(
) {
Icon(
imageVector = Icons.Default.Delete,
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
}
@ -346,9 +347,10 @@ private fun Stat(
)
Text(
modifier = Modifier.align(alignment = Alignment.Center),
style = MaterialTheme.typography.h4,
style = MaterialTheme.typography.h3,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
color = MaterialTheme.colors.primary,
text = characteristic.value
)
}
@ -402,6 +404,7 @@ private fun Roll(
)
Icon(
painter = painterResource(Res.drawable.ic_d20_32dp),
tint = MaterialTheme.colors.primary,
contentDescription = null,
)
}
@ -433,6 +436,7 @@ private fun Skill(
Text(
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.primary,
text = "$value",
)
Checkbox(modifier = Modifier.size(size = 32.dp), checked = false, onCheckedChange = { })

View file

@ -22,6 +22,7 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -85,7 +86,13 @@ fun CharacterSheetEditPage(
) {
CharacterSheetEdit(
form = viewModel.characterSheet.value,
onBack = { screen.popBackStack() },
onBack = remember {
if (viewModel.enableBack) {
{ screen.popBackStack() }
} else {
null
}
},
onNewSkill = viewModel::onSkill,
onNewCategory = viewModel::onNewRoll,
onSave = {
@ -103,7 +110,7 @@ fun CharacterSheetEditPage(
@Composable
fun CharacterSheetEdit(
form: CharacterSheetEditPageUio,
onBack: () -> Unit,
onBack: (() -> Unit)?,
onNewSkill: (CharacterSheetEditPageUio.SkillGroup) -> Unit,
onNewCategory: () -> Unit,
onSave: () -> Unit,
@ -118,14 +125,16 @@ fun CharacterSheetEdit(
text = stringResource(Res.string.character_sheet_edit__title),
)
},
navigationIcon = {
IconButton(
onClick = onBack,
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null,
)
navigationIcon = onBack?.let { action: () -> Unit ->
{
IconButton(
onClick = action,
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null,
)
}
}
}
)

View file

@ -18,6 +18,8 @@ class CharacterSheetEditViewModel(
private val repository = CharacterSheetRepository
private val factory = CharacterSheetEditFactory()
val enableBack = argument.enableBack
private val _characterSheet = mutableStateOf(
repository.characterSheetFlow(id = argument.id).value.let {
runBlocking { factory.convertToUio(it) }

View file

@ -14,8 +14,9 @@ class NetworkViewModel : ViewModel() {
private val repository = NetworkRepository
private val factory = NetworkFactory()
private val host = mutableStateOf("localhost")
private val port = mutableStateOf(27030)
private val host = mutableStateOf(NetworkRepository.DEFAULT_HOST)
private val port = mutableStateOf(NetworkRepository.DEFAULT_PORT)
val network: State<NetworkPageUio>
@Composable
@Stable

View file

@ -1,6 +1,7 @@
package com.pixelized.desktop.lwa.screen.roll
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
@ -11,15 +12,22 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ExposedDropdownMenuBox
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
@ -34,13 +42,23 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.pixelized.desktop.lwa.screen.roll.DifficultyUio.Difficulty
import com.pixelized.desktop.lwa.utils.DisableInteractionSource
import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_d20_32dp
import lwacharactersheet.composeapp.generated.resources.roll_page__dc__label
import lwacharactersheet.composeapp.generated.resources.roll_page__dc_easy__label
import lwacharactersheet.composeapp.generated.resources.roll_page__dc_hard__label
import lwacharactersheet.composeapp.generated.resources.roll_page__dc_impossible__label
import lwacharactersheet.composeapp.generated.resources.roll_page__dc_normal__label
import lwacharactersheet.composeapp.generated.resources.roll_page__roll__label
import lwacharactersheet.composeapp.generated.resources.roll_page__roll__success_label
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
@Stable
data class RollUio(
data class RollTitleUio(
val label: String,
val value: Int?,
)
@ -51,6 +69,16 @@ data class RollResultUio(
val value: Int,
)
@Stable
data class DifficultyUio(
val open: Boolean,
val difficulty: Difficulty,
) {
enum class Difficulty {
EASY, NORMAL, HARD, IMPOSSIBLE
}
}
@Composable
fun RollPage(
viewModel: RollViewModel = viewModel { RollViewModel() },
@ -72,24 +100,24 @@ fun RollPage(
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
text = "Jet de :",
text = stringResource(Res.string.roll_page__roll__label),
)
Text(
style = MaterialTheme.typography.h5,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
text = viewModel.roll.value.label,
text = viewModel.rollTitle.value.label,
)
viewModel.roll.value.value?.let {
viewModel.rollTitle.value.value?.let {
Text(
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
text = "Réussite en dessous de : ${it}",
text = stringResource(Res.string.roll_page__roll__success_label, it),
)
}
Column(
modifier = Modifier.fillMaxSize(),
modifier = Modifier.weight(weight = 1f),
verticalArrangement = Arrangement.spacedBy(
space = 24.dp,
alignment = Alignment.CenterVertically,
@ -114,7 +142,7 @@ fun RollPage(
contentDescription = null,
)
AnimatedContent(
targetState = viewModel.result.value?.value?.toString() ?: "",
targetState = viewModel.rollResult.value?.value?.toString() ?: "",
transitionSpec = {
val enter = fadeIn() + slideInVertically { 32 }
val exit = fadeOut() + slideOutVertically { -32 }
@ -137,7 +165,7 @@ fun RollPage(
}
}
AnimatedContent(
targetState = viewModel.result.value?.label ?: "",
targetState = viewModel.rollResult.value?.label ?: "",
transitionSpec = { fadeIn() togetherWith fadeOut() },
) { value ->
Text(
@ -148,5 +176,118 @@ fun RollPage(
)
}
}
viewModel.rollDifficulty.value?.let {
Box(
modifier = Modifier.clickable(
interactionSource = remember { DisableInteractionSource() },
indication = null,
onClick = {},
)
) {
Difficulty(
difficulty = it,
onToggle = { viewModel.toggleDifficulty() },
onDifficulty = { viewModel.onDifficulty(it) }
)
}
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun Difficulty(
modifier: Modifier = Modifier,
difficulty: DifficultyUio,
onToggle: () -> Unit,
onDifficulty: (Difficulty) -> Unit
) {
ExposedDropdownMenuBox(
modifier = modifier,
expanded = difficulty.open,
onExpandedChange = { onToggle() },
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Row(
modifier = Modifier.padding(
horizontal = 16.dp,
vertical = 8.dp,
),
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.body1,
text = stringResource(Res.string.roll_page__dc__label)
)
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.primary,
text = when (difficulty.difficulty) {
Difficulty.EASY -> stringResource(Res.string.roll_page__dc_easy__label)
Difficulty.NORMAL -> stringResource(Res.string.roll_page__dc_normal__label)
Difficulty.HARD -> stringResource(Res.string.roll_page__dc_hard__label)
Difficulty.IMPOSSIBLE -> stringResource(Res.string.roll_page__dc_impossible__label)
}
)
}
val rotation = animateFloatAsState(
targetValue = if (difficulty.open) -180f else 0f,
)
Icon(
modifier = Modifier.graphicsLayer {
rotationZ = rotation.value
},
imageVector = Icons.Default.ArrowDropDown,
contentDescription = null
)
}
ExposedDropdownMenu(
expanded = difficulty.open,
onDismissRequest = { onToggle() }
) {
DifficultyDropDownItem(
label = stringResource(Res.string.roll_page__dc_easy__label),
onClick = { onDifficulty(Difficulty.EASY) },
)
DifficultyDropDownItem(
label = stringResource(Res.string.roll_page__dc_normal__label),
onClick = { onDifficulty(Difficulty.NORMAL) },
)
DifficultyDropDownItem(
label = stringResource(Res.string.roll_page__dc_hard__label),
onClick = { onDifficulty(Difficulty.HARD) },
)
DifficultyDropDownItem(
label = stringResource(Res.string.roll_page__dc_impossible__label),
onClick = { onDifficulty(Difficulty.IMPOSSIBLE) },
)
}
}
}
@Composable
private fun DifficultyDropDownItem(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp),
label: String,
onClick: () -> Unit,
) {
DropdownMenuItem(
modifier = modifier,
onClick = onClick,
enabled = true,
contentPadding = contentPadding,
interactionSource = remember { MutableInteractionSource() },
content = {
Text(
color = MaterialTheme.colors.primary,
text = label,
)
},
)
}

View file

@ -12,6 +12,7 @@ import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.roll.RollHistoryRepository
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio
import com.pixelized.desktop.lwa.screen.roll.DifficultyUio.Difficulty
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
@ -20,6 +21,10 @@ import kotlinx.coroutines.runBlocking
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.roll_page__critical_failure
import lwacharactersheet.composeapp.generated.resources.roll_page__critical_success
import lwacharactersheet.composeapp.generated.resources.roll_page__dc_easy__label
import lwacharactersheet.composeapp.generated.resources.roll_page__dc_hard__label
import lwacharactersheet.composeapp.generated.resources.roll_page__dc_impossible__label
import lwacharactersheet.composeapp.generated.resources.roll_page__dc_normal__label
import lwacharactersheet.composeapp.generated.resources.roll_page__failure
import lwacharactersheet.composeapp.generated.resources.roll_page__special_success
import lwacharactersheet.composeapp.generated.resources.roll_page__success
@ -28,30 +33,32 @@ import org.jetbrains.compose.resources.getString
class RollViewModel : ViewModel() {
private val repository = RollHistoryRepository
private val _roll = mutableStateOf(RollUio(label = "", value = 0))
val roll: State<RollUio> get() = _roll
private lateinit var sheet: CharacterSheet
private lateinit var rollAction: String
private var rollSuccessValue: Int? = null
private var rollJob: Job? = null
private var rollStep: SkillStepUseCase.SkillStep? = null
private lateinit var rollAction: String
private lateinit var sheet: CharacterSheet
private val _rollTitle = mutableStateOf(RollTitleUio(label = "", value = 0))
val rollTitle: State<RollTitleUio> get() = _rollTitle
private val _rollResult = mutableStateOf<RollResultUio?>(null)
val rollResult: State<RollResultUio?> get() = _rollResult
val rollRotation = Animatable(0f)
private val _result = mutableStateOf<RollResultUio?>(null)
val result: State<RollResultUio?> get() = _result
private val _rollDifficulty = mutableStateOf<DifficultyUio?>(null)
val rollDifficulty: State<DifficultyUio?> get() = _rollDifficulty
fun prepareRoll(
sheet: CharacterSheetPageUio,
characteristic: CharacterSheetPageUio.Characteristic,
) {
val step = SkillStepUseCase.computeSkillStep(
skill = (characteristic.value.toIntOrNull() ?: 0) * 5
)
prepareRoll(
sheet = sheet,
label = characteristic.label,
rollAction = "1d100",
sheet = sheet,
rollStep = step,
rollSuccessValue = (characteristic.value.toIntOrNull() ?: 0) * 5,
)
}
@ -59,14 +66,11 @@ class RollViewModel : ViewModel() {
sheet: CharacterSheetPageUio,
node: CharacterSheetPageUio.Node,
) {
val step = SkillStepUseCase.computeSkillStep(
skill = node.value,
)
prepareRoll(
sheet = sheet,
label = node.label,
rollAction = "1d100",
sheet = sheet,
rollStep = step,
rollSuccessValue = node.value,
)
}
@ -75,34 +79,45 @@ class RollViewModel : ViewModel() {
roll: CharacterSheetPageUio.Roll,
) {
prepareRoll(
sheet = sheet,
label = roll.label,
rollAction = roll.value,
sheet = sheet,
rollStep = null,
rollSuccessValue = null,
)
}
private fun prepareRoll(
sheet: CharacterSheetPageUio,
label: String,
rollAction: String,
sheet: CharacterSheetPageUio,
rollStep: SkillStepUseCase.SkillStep?,
rollSuccessValue: Int?,
) {
runBlocking { rollRotation.snapTo(0f) }
this.rollStep = rollStep
this.rollAction = rollAction
this.sheet = CharacterSheetRepository.characterSheetFlow(id = sheet.id).value!!
_result.value = null
_roll.value = RollUio(
this.sheet = CharacterSheetRepository.characterSheetFlow(id = sheet.id).value!!
this.rollAction = rollAction
this.rollSuccessValue = rollSuccessValue
val rollStep = rollSuccessValue?.let {
SkillStepUseCase.computeSkillStep(skill = it)
}
_rollResult.value = null
_rollTitle.value = RollTitleUio(
label = label,
value = rollStep?.successRange?.last
)
_rollDifficulty.value = rollSuccessValue?.let {
DifficultyUio(
open = false,
Difficulty.NORMAL,
)
}
}
suspend fun roll() {
coroutineScope {
_result.value = null
_rollResult.value = null
rollJob?.cancel()
rollJob = launch {
@ -118,31 +133,81 @@ class RollViewModel : ViewModel() {
launch {
delay(500)
val rollStep = rollSuccessValue?.let {
SkillStepUseCase.computeSkillStep(
skill = when (_rollDifficulty.value?.difficulty) {
Difficulty.EASY -> it * 2
Difficulty.NORMAL -> it
Difficulty.HARD -> it / 2
Difficulty.IMPOSSIBLE -> it / 4
else -> it
}
)
}
val roll = if (rollAction == "1d100") {
RollUseCase.rollD100()
} else {
RollUseCase.roll(characterSheet = sheet, roll = rollAction)
}
val success = rollStep?.let {
when (roll) {
in it.criticalSuccessRange -> getString(resource = Res.string.roll_page__critical_success)
in it.specialSuccessRange -> getString(resource = Res.string.roll_page__special_success)
in it.successRange -> getString(resource = Res.string.roll_page__success)
in it.failureRange -> getString(resource = Res.string.roll_page__failure)
in it.criticalFailureRange -> getString(resource = Res.string.roll_page__critical_failure)
else -> ""
}
}
_result.value = RollResultUio(
label = rollStep?.let { rollStep ->
when (roll) {
in rollStep.criticalSuccessRange -> getString(resource = Res.string.roll_page__critical_success)
in rollStep.specialSuccessRange -> getString(resource = Res.string.roll_page__special_success)
in rollStep.successRange -> getString(resource = Res.string.roll_page__success)
in rollStep.failureRange -> getString(resource = Res.string.roll_page__failure)
in rollStep.criticalFailureRange -> getString(resource = Res.string.roll_page__critical_failure)
else -> ""
}
} ?: "",
_rollResult.value = RollResultUio(
label = success ?: "",
value = roll,
)
launch {
repository.share(label = _roll.value.label, roll = roll)
repository.share(
skillLabel = _rollTitle.value.label,
rollDifficulty = when (_rollDifficulty.value?.difficulty) {
Difficulty.EASY -> getString(Res.string.roll_page__dc_easy__label)
Difficulty.NORMAL -> getString(Res.string.roll_page__dc_normal__label)
Difficulty.HARD -> getString(Res.string.roll_page__dc_hard__label)
Difficulty.IMPOSSIBLE -> getString(Res.string.roll_page__dc_impossible__label)
else -> null
},
rollValue = roll,
rollSuccessLimit = rollStep?.successRange?.last,
resultLabel = success,
)
}
}
}
}
}
fun toggleDifficulty() {
_rollDifficulty.value = _rollDifficulty.value?.copy(
open = _rollDifficulty.value?.open?.not() ?: false
)
}
fun onDifficulty(difficulty: Difficulty) {
_rollDifficulty.value = DifficultyUio(
open = false,
difficulty = difficulty,
)
val rollStep = rollSuccessValue?.let {
SkillStepUseCase.computeSkillStep(
skill = when (_rollDifficulty.value?.difficulty) {
Difficulty.EASY -> it * 2
Difficulty.NORMAL -> it
Difficulty.HARD -> it / 2
Difficulty.IMPOSSIBLE -> it / 4
else -> it
}
)
}
_rollTitle.value = _rollTitle.value.copy(
value = rollStep?.successRange?.last
)
}
}

View file

@ -0,0 +1,111 @@
package com.pixelized.desktop.lwa.screen.rollhistory
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.roll_history__item__difficulty
import lwacharactersheet.composeapp.generated.resources.roll_history__item__throw
import org.jetbrains.compose.resources.stringResource
@Stable
data class RollHistoryItemUio(
val from: String,
val skillLabel: String,
val rollDifficulty: String?,
val rollValue: Int,
val rollSuccessLimit: Int?,
val resultLabel: String?,
)
@Composable
fun RollHistoryItem(
modifier: Modifier = Modifier,
roll: RollHistoryItemUio,
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier.weight(1f)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold,
text = roll.from,
)
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.caption,
text = stringResource(Res.string.roll_history__item__throw),
)
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = roll.skillLabel,
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
roll.rollDifficulty?.let {
Text(
style = MaterialTheme.typography.caption,
text = stringResource(Res.string.roll_history__item__difficulty),
)
Text(
style = MaterialTheme.typography.caption,
text = it,
)
Text(
style = MaterialTheme.typography.caption,
text = "-",
)
}
roll.resultLabel?.let {
Text(
style = MaterialTheme.typography.caption,
fontWeight = FontWeight.Bold,
text = it,
)
}
}
}
Row(
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.h6,
fontWeight = FontWeight.Bold,
text = "${roll.rollValue}",
)
roll.rollSuccessLimit?.let {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.caption,
fontWeight = FontWeight.Light,
text = "/ $it",
)
}
}
}
}

View file

@ -2,14 +2,13 @@ package com.pixelized.desktop.lwa.screen.rollhistory
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.material.Text
@ -17,10 +16,8 @@ import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
@ -29,12 +26,6 @@ import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.roll_history__title
import org.jetbrains.compose.resources.stringResource
@Stable
data class RollItemUio(
val from: String,
val label: String,
val roll: Int,
)
@Composable
fun RollHistoryPage(
@ -58,7 +49,7 @@ fun RollHistoryPage(
@Composable
private fun RollHistoryContent(
modifier: Modifier = Modifier,
rolls: State<List<RollItemUio>>,
rolls: State<List<RollHistoryItemUio>>,
onBack: () -> Unit,
) {
Scaffold(
@ -92,10 +83,11 @@ private fun RollHistoryContent(
state = state,
reverseLayout = true,
contentPadding = PaddingValues(all = 24.dp),
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
) {
items(items = rolls.value) {
RollItem(
RollHistoryItem(
modifier = Modifier.fillMaxWidth(),
roll = it
)
}
@ -104,32 +96,3 @@ private fun RollHistoryContent(
)
}
@Composable
private fun RollItem(
modifier: Modifier = Modifier,
roll: RollItemUio,
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Thin,
text = roll.from,
)
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Light,
text = roll.label,
)
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold,
text = "${roll.roll}",
)
}
}

View file

@ -12,8 +12,8 @@ class RollHistoryViewModel : ViewModel() {
private val repository = RollHistoryRepository
private val _rolls = mutableStateOf((emptyList<RollItemUio>()))
val rolls: State<List<RollItemUio>> get() = _rolls
private val _rolls = mutableStateOf((emptyList<RollHistoryItemUio>()))
val rolls: State<List<RollHistoryItemUio>> get() = _rolls
init {
viewModelScope.launch {
@ -22,10 +22,13 @@ class RollHistoryViewModel : ViewModel() {
_rolls.value = _rolls.value.toMutableList().apply {
add(
index = 0,
element = RollItemUio(
element = RollHistoryItemUio(
from = it.from,
label = content.label,
roll = content.roll
skillLabel = content.skillLabel,
rollDifficulty = content.rollDifficulty,
resultLabel = content.resultLabel,
rollValue = content.rollValue,
rollSuccessLimit = content.rollSuccessLimit,
)
)
}

View file

@ -0,0 +1,14 @@
package com.pixelized.desktop.lwa.utils
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
class DisableInteractionSource : MutableInteractionSource {
override val interactions: Flow<Interaction> = emptyFlow()
override suspend fun emit(interaction: Interaction) = Unit
override fun tryEmit(interaction: Interaction): Boolean = false
}

View file

@ -6,7 +6,7 @@ junit = "4.13.2"
compose-multiplatform = "1.7.0"
androidx-lifecycle = "2.8.3"
androidx-navigation = "2.8.0-alpha10"
ktor_version = "3.0.1"
ktor_version = "3.0.0"
[plugins]
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
@ -24,6 +24,7 @@ androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle
androidx-navigation-compose = { group = "org.jetbrains.androidx.navigation", name = "navigation-compose", version.ref = "androidx-navigation" }
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-json" }
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor_version" }
ktor-client-cio = { group = 'io.ktor', name = "ktor-client-cio", version.ref = "ktor_version" }
ktor-client-websockets = { group = 'io.ktor', name = "ktor-client-websockets", version.ref = "ktor_version" }