From ca456b55d92018ce7d6a658f7f752f19fc4700bb Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Sun, 10 Nov 2024 17:26:58 +0100 Subject: [PATCH] Add roll history page --- build.gradle.kts | 4 +- composeApp/build.gradle.kts | 3 +- .../composeResources/values/strings.xml | 17 +- .../kotlin/com/pixelized/desktop/lwa/App.kt | 5 +- .../CharacterSheetEditDestination.kt | 21 ++- .../repository/network/NetworkRepository.kt | 3 + .../lwa/repository/network/helper/Server.kt | 2 +- .../network/protocol/RollMessage.kt | 9 +- .../repository/roll/RollHistoryRepository.kt | 15 +- .../detail/CharacterSheetPage.kt | 16 +- .../edit/CharacterSheetEditPage.kt | 29 ++-- .../edit/CharacterSheetEditViewModel.kt | 2 + .../lwa/screen/network/NetworkViewModel.kt | 5 +- .../desktop/lwa/screen/roll/RollPage.kt | 157 +++++++++++++++++- .../desktop/lwa/screen/roll/RollViewModel.kt | 145 +++++++++++----- .../lwa/screen/rollhistory/RollHistoryItem.kt | 111 +++++++++++++ .../lwa/screen/rollhistory/RollHistoryPage.kt | 47 +----- .../rollhistory/RollHistoryViewModel.kt | 13 +- .../lwa/utils/DisableInteractionSource.kt | 14 ++ gradle/libs.versions.toml | 3 +- 20 files changed, 488 insertions(+), 133 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryItem.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/DisableInteractionSource.kt diff --git a/build.gradle.kts b/build.gradle.kts index 7d61caf..eb8e169 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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 } \ No newline at end of file diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 359eca4..3f09148 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -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 { diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 1407017..99445d2 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -1,14 +1,21 @@ Créer une feuille de personnage - Configuration réseau - Consulter l'historique des lancés + Configuration de la table + Consulter l'historique des lancers + Jet de : + Réussite si lancer inférieur ou égale à : %1$s Réussite critique Réussite spéciale Réussite Échec Échec critique + Dégré de difficulté : + Facile + Normal + Difficile + Impossible Création de personnage Nom @@ -68,7 +75,7 @@ Occupations Compétences magiques - Configuration réseau + Configuration de la table Nom du joueur host port @@ -83,5 +90,7 @@ Client Aucun - Historique des lancés + Historique des lancers + lance + Difficulté \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt index 8d33b3c..650c8d0 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt @@ -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, + ) ) } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/CharacterSheetEditDestination.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/CharacterSheetEditDestination.kt index fe069af..2429a1f 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/CharacterSheetEditDestination.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/CharacterSheetEditDestination.kt @@ -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(CHARACTER_ID) + id = savedStateHandle.get(CHARACTER_ID), + enableBack = savedStateHandle.get(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) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt index 021d093..011b704 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt @@ -27,6 +27,9 @@ typealias Server = EmbeddedServer Unit ): EmbeddedServer { return embeddedServer( diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/RollMessage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/RollMessage.kt index 1893b26..fddf0a8 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/RollMessage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/RollMessage.kt @@ -4,6 +4,9 @@ import kotlinx.serialization.Serializable @Serializable data class RollMessage( - val label: String, - val roll: Int, -): MessageContent \ No newline at end of file + val skillLabel: String, + val resultLabel: String?, + val rollDifficulty: String?, + val rollValue: Int, + val rollSuccessLimit: Int?, +) : MessageContent \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll/RollHistoryRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll/RollHistoryRepository.kt index 50557b2..bd7d474 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll/RollHistoryRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll/RollHistoryRepository.kt @@ -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, + ) ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt index f371989..5c1acdb 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt @@ -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 = { }) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt index 94d0845..2f66917 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt @@ -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, + ) + } } } ) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditViewModel.kt index 823f529..ff01c8f 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditViewModel.kt @@ -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) } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/network/NetworkViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/network/NetworkViewModel.kt index 1ce3c31..c1b45d8 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/network/NetworkViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/network/NetworkViewModel.kt @@ -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 @Composable @Stable diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollPage.kt index 189e2de..a9a14d7 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollPage.kt @@ -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, + ) + }, + ) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt index 20bfb00..843d64a 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt @@ -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 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 get() = _rollTitle + + private val _rollResult = mutableStateOf(null) + val rollResult: State get() = _rollResult + val rollRotation = Animatable(0f) - private val _result = mutableStateOf(null) - val result: State get() = _result + private val _rollDifficulty = mutableStateOf(null) + val rollDifficulty: State 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 + ) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryItem.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryItem.kt new file mode 100644 index 0000000..7d0939a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryItem.kt @@ -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", + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryPage.kt index 2023450..e20677f 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryPage.kt @@ -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>, + rolls: State>, 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}", - ) - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryViewModel.kt index 356fe50..898c81e 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryViewModel.kt @@ -12,8 +12,8 @@ class RollHistoryViewModel : ViewModel() { private val repository = RollHistoryRepository - private val _rolls = mutableStateOf((emptyList())) - val rolls: State> get() = _rolls + private val _rolls = mutableStateOf((emptyList())) + val rolls: State> 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, ) ) } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/DisableInteractionSource.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/DisableInteractionSource.kt new file mode 100644 index 0000000..9eddde6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/DisableInteractionSource.kt @@ -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 = emptyFlow() + + override suspend fun emit(interaction: Interaction) = Unit + + override fun tryEmit(interaction: Interaction): Boolean = false +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e2f7e34..ac39250 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" }