Update the app with some features skeleton.
Change the navigation system Add a basic file system management to save the charactersheet. Add a common source code directory & commonTest module. Add test to the business class.
This commit is contained in:
parent
d74a5fcd7c
commit
65aa53890f
34 changed files with 1412 additions and 541 deletions
|
|
@ -1,35 +1,11 @@
|
|||
package com.pixelized.desktop.lwa
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.material.Button
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.rememberWindowState
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheet
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetUio
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetViewModel
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEdit
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEditUio
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.FieldUio
|
||||
import com.pixelized.desktop.lwa.screen.overlay.BlurOverlay
|
||||
import com.pixelized.desktop.lwa.screen.overlay.BlurOverlayViewModel
|
||||
import com.pixelized.desktop.lwa.screen.roll.RollPage
|
||||
import com.pixelized.desktop.lwa.screen.roll.RollViewModel
|
||||
import com.pixelized.desktop.lwa.navigation.MainNavHost
|
||||
import com.pixelized.desktop.lwa.screen.main.MainPage
|
||||
import com.pixelized.desktop.lwa.theme.LwaTheme
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
|
||||
|
|
@ -40,120 +16,128 @@ fun App() {
|
|||
Surface(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
val sheetViewModel = viewModel { CharacterSheetViewModel() }
|
||||
val overlayViewModel = viewModel { BlurOverlayViewModel() }
|
||||
val rollViewModel = viewModel { RollViewModel() }
|
||||
|
||||
val edit = remember { mutableStateOf<CharacterSheetEditUio?>(null) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(all = 16.dp),
|
||||
) {
|
||||
Row {
|
||||
Button(
|
||||
onClick = sheetViewModel::showCharacterSheet,
|
||||
) {
|
||||
Text(text = "Koryas Tissenpa")
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
edit.value = CharacterSheetEditUio.create(
|
||||
sheet = CharacterSheetUio.Koryas,
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Edit,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
Button(
|
||||
onClick = { edit.value = CharacterSheetEditUio.Default },
|
||||
) {
|
||||
Text(text = "Créer une feuille de personnage")
|
||||
}
|
||||
}
|
||||
|
||||
sheetViewModel.sheet.value?.let { sheet ->
|
||||
Window(
|
||||
onCloseRequest = sheetViewModel::hideCharacterSheet,
|
||||
state = rememberWindowState(
|
||||
width = 320.dp + 64.dp,
|
||||
height = 900.dp,
|
||||
),
|
||||
title = "LwaCharacterSheet",
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
BlurOverlay(
|
||||
viewModel = overlayViewModel,
|
||||
overlay = {
|
||||
RollPage(
|
||||
viewModel = rollViewModel,
|
||||
onDismissRequest = overlayViewModel::hide,
|
||||
)
|
||||
},
|
||||
content = {
|
||||
CharacterSheet(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
width = 320.dp,
|
||||
characterSheet = sheet,
|
||||
onCharacteristic = { characteristic ->
|
||||
rollViewModel.prepareRoll(characteristic = characteristic)
|
||||
overlayViewModel.show()
|
||||
},
|
||||
onSkill = { node ->
|
||||
rollViewModel.prepareRoll(node = node)
|
||||
overlayViewModel.show()
|
||||
}
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
edit.value?.let { sheet ->
|
||||
Window(
|
||||
onCloseRequest = { edit.value = null },
|
||||
state = rememberWindowState(
|
||||
width = 320.dp + 64.dp,
|
||||
height = 900.dp,
|
||||
),
|
||||
title = "LwaCharacterSheet",
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
CharacterSheetEdit(
|
||||
form = sheet,
|
||||
onSkill = { skill ->
|
||||
edit.value = sheet.copy(
|
||||
groups = sheet.groups.map { group ->
|
||||
if (skill.title == group.title) {
|
||||
group.copy(
|
||||
fields = mutableListOf<FieldUio>().apply {
|
||||
addAll(group.fields)
|
||||
add(
|
||||
FieldUio.create(
|
||||
label = "",
|
||||
valuePlaceHolder = { "40" },
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
group
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
MainNavHost()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// val sheetViewModel = viewModel { CharacterSheetViewModel() }
|
||||
// val overlayViewModel = viewModel { BlurOverlayViewModel() }
|
||||
// val rollViewModel = viewModel { RollViewModel() }
|
||||
//
|
||||
// val edit = remember { mutableStateOf<CharacterSheetEditUio?>(null) }
|
||||
//
|
||||
//
|
||||
//
|
||||
// Column(
|
||||
// modifier = Modifier.padding(all = 16.dp),
|
||||
// ) {
|
||||
// Row {
|
||||
// Button(
|
||||
// onClick = sheetViewModel::showCharacterSheet,
|
||||
// ) {
|
||||
// Text(text = "Koryas Tissenpa")
|
||||
// }
|
||||
// IconButton(
|
||||
// onClick = {
|
||||
// edit.value = CharacterSheetEditUio.create(
|
||||
// sheet = CharacterSheetUio.Koryas,
|
||||
// )
|
||||
// }
|
||||
// ) {
|
||||
// Icon(
|
||||
// imageVector = Icons.Default.Edit,
|
||||
// contentDescription = null
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// Button(
|
||||
// onClick = { edit.value = CharacterSheetEditUio.Default },
|
||||
// ) {
|
||||
// Text(text = "Créer une feuille de personnage")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// sheetViewModel.sheet.value?.let { sheet ->
|
||||
// Window(
|
||||
// onCloseRequest = sheetViewModel::hideCharacterSheet,
|
||||
// state = rememberWindowState(
|
||||
// width = 320.dp + 64.dp,
|
||||
// height = 900.dp,
|
||||
// ),
|
||||
// title = "LwaCharacterSheet",
|
||||
// ) {
|
||||
// Surface(
|
||||
// modifier = Modifier.fillMaxSize()
|
||||
// ) {
|
||||
// BlurOverlay(
|
||||
// viewModel = overlayViewModel,
|
||||
// overlay = {
|
||||
// RollPage(
|
||||
// viewModel = rollViewModel,
|
||||
// onDismissRequest = overlayViewModel::hide,
|
||||
// )
|
||||
// },
|
||||
// content = {
|
||||
// CharacterSheet(
|
||||
// modifier = Modifier.fillMaxWidth(),
|
||||
// width = 320.dp,
|
||||
// characterSheet = sheet,
|
||||
// onCharacteristic = { characteristic ->
|
||||
// rollViewModel.prepareRoll(characteristic = characteristic)
|
||||
// overlayViewModel.show()
|
||||
// },
|
||||
// onSkill = { node ->
|
||||
// rollViewModel.prepareRoll(node = node)
|
||||
// overlayViewModel.show()
|
||||
// }
|
||||
// )
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// edit.value?.let { sheet ->
|
||||
// Window(
|
||||
// onCloseRequest = { edit.value = null },
|
||||
// state = rememberWindowState(
|
||||
// width = 320.dp + 64.dp,
|
||||
// height = 900.dp,
|
||||
// ),
|
||||
// title = "LwaCharacterSheet",
|
||||
// ) {
|
||||
// Surface(
|
||||
// modifier = Modifier.fillMaxSize(),
|
||||
// ) {
|
||||
// CharacterSheetEdit(
|
||||
// form = sheet,
|
||||
// onSkill = { skill ->
|
||||
// edit.value = sheet.copy(
|
||||
// groups = sheet.groups.map { group ->
|
||||
// if (skill.title == group.title) {
|
||||
// group.copy(
|
||||
// fields = mutableListOf<FieldUio>().apply {
|
||||
// addAll(group.fields)
|
||||
// add(
|
||||
// FieldUio.create(
|
||||
// label = "",
|
||||
// valuePlaceHolder = { "40" },
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// )
|
||||
// } else {
|
||||
// group
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.pixelized.desktop.lwa.business
|
||||
|
||||
object RollUseCase {
|
||||
private val d100 = (1..100)
|
||||
|
||||
/**
|
||||
* (Math.random() * 100 + 1).toInt()
|
||||
* TODO : test.
|
||||
*/
|
||||
fun rollD100(): Int {
|
||||
return d100.random()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
package com.pixelized.desktop.lwa.business
|
||||
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
object SkillStepUseCase {
|
||||
|
||||
data class SkillStep(
|
||||
val criticalSuccessRange: IntRange,
|
||||
val specialSuccessRange: IntRange,
|
||||
val successRange: IntRange,
|
||||
val failureRange: IntRange,
|
||||
val criticalFailureRange: IntRange,
|
||||
) {
|
||||
constructor(
|
||||
criticalSuccess: Pair<Int, Int>?,
|
||||
specialSuccess: Pair<Int, Int>,
|
||||
success: Pair<Int, Int>,
|
||||
failure: Pair<Int, Int>?,
|
||||
criticalFailure: Pair<Int, Int>,
|
||||
) : this(
|
||||
criticalSuccessRange = criticalSuccess
|
||||
?.let { IntRange(it.first, it.second) }
|
||||
?: IntRange(-1, -1),
|
||||
specialSuccessRange = specialSuccess
|
||||
.let { IntRange(it.first, it.second) },
|
||||
successRange = success
|
||||
.let { IntRange(it.first, it.second) },
|
||||
failureRange = failure
|
||||
?.let { IntRange(it.first, it.second) } ?: IntRange(-1, -1),
|
||||
criticalFailureRange = criticalFailure
|
||||
.let { IntRange(it.first, it.second) },
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to compute the range in which a roll is a either critical, special, success or failure.
|
||||
* TODO : test.
|
||||
*/
|
||||
fun computeSkillStep(skill: Int): SkillStep {
|
||||
val criticalSuccess: Pair<Int, Int>? = when (skill) {
|
||||
in (0..5) -> null
|
||||
in (10..25) -> 1 to 1
|
||||
in (30..45) -> 1 to 2
|
||||
in (50..65) -> 1 to 3
|
||||
in (70..85) -> 1 to 4
|
||||
in (90..100) -> 1 to 5
|
||||
else -> 1 to skill * 5 / 100
|
||||
}
|
||||
val specialSuccess: Pair<Int, Int> = when (skill) {
|
||||
0, 5 -> 1 to 1
|
||||
10 -> 2 to 2
|
||||
15 -> ((criticalSuccess?.second ?: 0) + 1) to 3
|
||||
20 -> ((criticalSuccess?.second ?: 0) + 1) to 4
|
||||
25 -> ((criticalSuccess?.second ?: 0) + 1) to 5
|
||||
30 -> ((criticalSuccess?.second ?: 0) + 1) to 6
|
||||
35 -> ((criticalSuccess?.second ?: 0) + 1) to 7
|
||||
40 -> ((criticalSuccess?.second ?: 0) + 1) to 8
|
||||
45 -> ((criticalSuccess?.second ?: 0) + 1) to 9
|
||||
50 -> ((criticalSuccess?.second ?: 0) + 1) to 10
|
||||
55 -> ((criticalSuccess?.second ?: 0) + 1) to 11
|
||||
60 -> ((criticalSuccess?.second ?: 0) + 1) to 12
|
||||
65 -> ((criticalSuccess?.second ?: 0) + 1) to 13
|
||||
70 -> ((criticalSuccess?.second ?: 0) + 1) to 15
|
||||
75 -> ((criticalSuccess?.second ?: 0) + 1) to 15
|
||||
80 -> ((criticalSuccess?.second ?: 0) + 1) to 16
|
||||
85 -> ((criticalSuccess?.second ?: 0) + 1) to 17
|
||||
90 -> ((criticalSuccess?.second ?: 0) + 1) to 18
|
||||
95 -> ((criticalSuccess?.second ?: 0) + 1) to 19
|
||||
100 -> ((criticalSuccess?.second ?: 0) + 1) to 20
|
||||
else -> ((criticalSuccess?.second ?: 0) + 1) to skill * 20 / 100
|
||||
}
|
||||
val success: Pair<Int, Int> = (specialSuccess.second + 1) to max(5, min(99, skill))
|
||||
val criticalFailure: Pair<Int, Int> = when (skill) {
|
||||
0, 5, 10 -> 96 to 100
|
||||
15, 20, 25, 30 -> 97 to 100
|
||||
35, 40, 45, 50 -> 98 to 100
|
||||
55, 60, 65, 70 -> 99 to 100
|
||||
else -> 100 to 100
|
||||
}
|
||||
val failure: Pair<Int, Int>? = if (skill >= 100) {
|
||||
null
|
||||
} else {
|
||||
success.second + 1 to criticalFailure.first - 1
|
||||
}
|
||||
|
||||
return SkillStep(
|
||||
criticalSuccess = criticalSuccess,
|
||||
specialSuccess = specialSuccess,
|
||||
success = success,
|
||||
failure = failure,
|
||||
criticalFailure = criticalFailure,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.composable
|
||||
package com.pixelized.desktop.lwa.composable.decoratedBox
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.screen.overlay
|
||||
package com.pixelized.desktop.lwa.composable.overlay
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.fadeIn
|
||||
|
|
@ -10,21 +10,37 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
|
||||
@Stable
|
||||
data class BlurOverlayTransitionUio(
|
||||
val blur: State<Dp>,
|
||||
val background: State<Color>,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun BlurOverlay(
|
||||
viewModel: BlurOverlayViewModel = viewModel { BlurOverlayViewModel() },
|
||||
overlay: @Composable BoxScope.() -> Unit,
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
val transition = viewModel.transition
|
||||
Box {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.blur(radius = viewModel.blur.value),
|
||||
.blur(radius = transition.blur.value)
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
drawRect(color = transition.background.value)
|
||||
},
|
||||
content = content,
|
||||
)
|
||||
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.pixelized.desktop.lwa.composable.overlay
|
||||
|
||||
import androidx.compose.animation.animateColor
|
||||
import androidx.compose.animation.core.animateDp
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class BlurOverlayViewModel : ViewModel() {
|
||||
private val _overlay = mutableStateOf(false)
|
||||
val overlay: State<Boolean>
|
||||
get() = _overlay
|
||||
|
||||
val transition: BlurOverlayTransitionUio
|
||||
@Composable
|
||||
get() {
|
||||
val transition = updateTransition(_overlay.value)
|
||||
val blur = transition.animateDp {
|
||||
when (it) {
|
||||
true -> 8.dp
|
||||
else -> 0.dp
|
||||
}
|
||||
}
|
||||
val background = transition.animateColor {
|
||||
when (it) {
|
||||
true -> Color.Black.copy(alpha = 0.6f)
|
||||
else -> Color.Black.copy(alpha = 0f)
|
||||
}
|
||||
}
|
||||
return remember {
|
||||
BlurOverlayTransitionUio(
|
||||
blur = blur,
|
||||
background = background,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun show() {
|
||||
_overlay.value = true
|
||||
}
|
||||
|
||||
fun hide() {
|
||||
_overlay.value = false
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,21 @@
|
|||
package com.pixelized.desktop.lwa
|
||||
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import androidx.compose.ui.window.rememberWindowState
|
||||
|
||||
fun main() = application {
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
title = "LwaCharacterSheet",
|
||||
) {
|
||||
App()
|
||||
fun main() {
|
||||
application {
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
state = rememberWindowState(
|
||||
width = 320.dp + 64.dp,
|
||||
height = 900.dp,
|
||||
),
|
||||
title = "LwaCharacterSheet",
|
||||
) {
|
||||
App()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.pixelized.desktop.lwa.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.pixelized.desktop.lwa.navigation.destination.MainDestination
|
||||
import com.pixelized.desktop.lwa.navigation.destination.composableCharacterSheetEditPage
|
||||
import com.pixelized.desktop.lwa.navigation.destination.composableMainPage
|
||||
|
||||
val LocalScreen = compositionLocalOf<NavHostController> {
|
||||
error("MainNavHost controller is not yet ready")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainNavHost(
|
||||
controller: NavHostController = rememberNavController(),
|
||||
startDestination: String = MainDestination.navigationRoute(),
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalScreen provides controller,
|
||||
) {
|
||||
NavHost(
|
||||
navController = controller,
|
||||
startDestination = startDestination,
|
||||
) {
|
||||
composableMainPage()
|
||||
|
||||
composableCharacterSheetEditPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.pixelized.desktop.lwa.navigation.destination
|
||||
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEdit
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEditPage
|
||||
|
||||
object CharacterSheetEditDestination {
|
||||
private const val ROUTE = "character.sheet.edit"
|
||||
|
||||
fun baseRoute() = ROUTE
|
||||
fun navigationRoute() = ROUTE
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.composableCharacterSheetEditPage() {
|
||||
composable(
|
||||
route = CharacterSheetEditDestination.baseRoute(),
|
||||
) {
|
||||
CharacterSheetEditPage()
|
||||
}
|
||||
}
|
||||
|
||||
fun NavHostController.navigateToCharacterSheetEdit() {
|
||||
val route = CharacterSheetEditDestination.navigationRoute()
|
||||
navigate(route = route)
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.pixelized.desktop.lwa.navigation.destination
|
||||
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import com.pixelized.desktop.lwa.screen.main.MainPage
|
||||
|
||||
object MainDestination {
|
||||
private const val ROUTE = "main"
|
||||
|
||||
fun baseRoute() = ROUTE
|
||||
fun navigationRoute() = ROUTE
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.composableMainPage() {
|
||||
composable(
|
||||
route = MainDestination.baseRoute(),
|
||||
) {
|
||||
MainPage()
|
||||
}
|
||||
}
|
||||
|
||||
fun NavHostController.navigateToMainPage() {
|
||||
val route = MainDestination.navigationRoute()
|
||||
navigate(route = route)
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
|
|
@ -28,11 +27,11 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.composable.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetUio.Node.Type
|
||||
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetPageUio.Node.Type
|
||||
|
||||
@Stable
|
||||
data class CharacterSheetUio(
|
||||
data class CharacterSheetPageUio(
|
||||
val name: String,
|
||||
val characteristics: List<Characteristic>,
|
||||
val subCharacteristics: List<Characteristic>,
|
||||
|
|
@ -61,7 +60,7 @@ data class CharacterSheetUio(
|
|||
}
|
||||
|
||||
companion object {
|
||||
val Koryas = CharacterSheetUio(
|
||||
val Koryas = CharacterSheetPageUio(
|
||||
name = "Koryas Tissenpa",
|
||||
characteristics = listOf(
|
||||
Characteristic(label = "Force", value = "10"),
|
||||
|
|
@ -113,13 +112,13 @@ data class CharacterSheetUio(
|
|||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun CharacterSheet(
|
||||
fun CharacterSheetPage(
|
||||
modifier: Modifier,
|
||||
scrollState: ScrollState = rememberScrollState(),
|
||||
width: Dp = 320.dp,
|
||||
characterSheet: CharacterSheetUio = CharacterSheetUio.Koryas,
|
||||
onCharacteristic: (characteristic: CharacterSheetUio.Characteristic) -> Unit,
|
||||
onSkill: (skill: CharacterSheetUio.Node) -> Unit,
|
||||
characterSheet: CharacterSheetPageUio = CharacterSheetPageUio.Koryas,
|
||||
onCharacteristic: (characteristic: CharacterSheetPageUio.Characteristic) -> Unit,
|
||||
onSkill: (skill: CharacterSheetPageUio.Node) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
|
@ -247,7 +246,7 @@ fun CharacterSheet(
|
|||
private fun Stat(
|
||||
modifier: Modifier = Modifier,
|
||||
paddingValues: PaddingValues = PaddingValues(all = 8.dp),
|
||||
characteristic: CharacterSheetUio.Characteristic,
|
||||
characteristic: CharacterSheetPageUio.Characteristic,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
DecoratedBox(
|
||||
|
|
@ -277,7 +276,7 @@ private fun Stat(
|
|||
private fun Characteristics(
|
||||
modifier: Modifier = Modifier,
|
||||
paddingValues: PaddingValues = PaddingValues(horizontal = 8.dp),
|
||||
characteristic: CharacterSheetUio.Characteristic,
|
||||
characteristic: CharacterSheetPageUio.Characteristic,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -6,11 +6,11 @@ import androidx.lifecycle.ViewModel
|
|||
|
||||
class CharacterSheetViewModel : ViewModel() {
|
||||
|
||||
private val _sheet = mutableStateOf<CharacterSheetUio?>(null)
|
||||
val sheet: State<CharacterSheetUio?> get() = _sheet
|
||||
private val _sheet = mutableStateOf<CharacterSheetPageUio?>(null)
|
||||
val sheet: State<CharacterSheetPageUio?> get() = _sheet
|
||||
|
||||
fun showCharacterSheet() {
|
||||
_sheet.value = CharacterSheetUio.Koryas
|
||||
_sheet.value = CharacterSheetPageUio.Koryas
|
||||
}
|
||||
|
||||
fun hideCharacterSheet() {
|
||||
|
|
|
|||
|
|
@ -1,323 +0,0 @@
|
|||
package com.pixelized.desktop.lwa.screen.characterSheet.edit
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
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.Add
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.composable.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetUio
|
||||
import kotlin.math.max
|
||||
import kotlin.math.truncate
|
||||
|
||||
@Stable
|
||||
data class CharacterSheetEditUio(
|
||||
val name: FieldUio,
|
||||
val groups: List<Group>,
|
||||
) {
|
||||
@Stable
|
||||
data class Group(
|
||||
val title: String,
|
||||
val editable: Boolean = false,
|
||||
val fields: List<FieldUio>,
|
||||
)
|
||||
|
||||
companion object {
|
||||
val Default = run {
|
||||
val strField = FieldUio.create(label = "Force", valuePlaceHolder = { "0" })
|
||||
fun str(): Int = strField.value.value.toIntOrNull() ?: 0
|
||||
val dexField = FieldUio.create(label = "Dextérité", valuePlaceHolder = { "0" })
|
||||
fun dex(): Int = dexField.value.value.toIntOrNull() ?: 0
|
||||
val conField = FieldUio.create(label = "Constitution", valuePlaceHolder = { "0" })
|
||||
fun con(): Int = conField.value.value.toIntOrNull() ?: 0
|
||||
val vitField = FieldUio.create(label = "Taille", valuePlaceHolder = { "0" })
|
||||
fun vit(): Int = vitField.value.value.toIntOrNull() ?: 0
|
||||
val intField = FieldUio.create(label = "Intelligence", valuePlaceHolder = { "0" })
|
||||
fun int(): Int = intField.value.value.toIntOrNull() ?: 0
|
||||
val powField = FieldUio.create(label = "Pouvoir", valuePlaceHolder = { "0" })
|
||||
fun pow(): Int = powField.value.value.toIntOrNull() ?: 0
|
||||
val chaField = FieldUio.create(label = "Charisme", valuePlaceHolder = { "0" })
|
||||
fun cha(): Int = chaField.value.value.toIntOrNull() ?: 0
|
||||
|
||||
CharacterSheetEditUio(
|
||||
name = FieldUio.create(
|
||||
useLabelAsPlaceholder = true,
|
||||
label = "Name",
|
||||
),
|
||||
groups = listOf(
|
||||
Group(
|
||||
title = "Charactéristiques",
|
||||
fields = listOf(
|
||||
strField,
|
||||
dexField,
|
||||
conField,
|
||||
vitField,
|
||||
intField,
|
||||
powField,
|
||||
chaField
|
||||
),
|
||||
),
|
||||
Group(
|
||||
title = "Charactéristiques dérivées",
|
||||
fields = listOf(
|
||||
FieldUio.create(
|
||||
label = "Déplacement",
|
||||
valuePlaceHolder = { "10" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Points de vie",
|
||||
valuePlaceHolder = { "${(con() + vit()) / 2}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Points de pouvoir",
|
||||
valuePlaceHolder = { "${pow()}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Bonus aux dégats",
|
||||
valuePlaceHolder = {
|
||||
val bonus = str() + vit()
|
||||
when {
|
||||
bonus < 12 -> "-1d6"
|
||||
bonus in 12..17 -> "-1d4"
|
||||
bonus in 18..22 -> "-0"
|
||||
bonus in 23..29 -> "1d4"
|
||||
bonus in 30..39 -> "1d6"
|
||||
else -> "2d6"
|
||||
}
|
||||
},
|
||||
),
|
||||
FieldUio.create(label = "Armure", valuePlaceHolder = { "0" }),
|
||||
),
|
||||
),
|
||||
Group(
|
||||
title = "Compétances",
|
||||
editable = true,
|
||||
fields = listOf(
|
||||
FieldUio.create(
|
||||
label = "Bagarre",
|
||||
valuePlaceHolder = { trunc(dex() * 2) },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Esquive",
|
||||
valuePlaceHolder = { trunc(dex() * 2) }
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Saisie",
|
||||
valuePlaceHolder = { trunc(str() + vit()) },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Lancer",
|
||||
valuePlaceHolder = { trunc(str() + dex()) },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Athlétisme",
|
||||
valuePlaceHolder = { trunc(str() + con() * 2) },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Acrobatie",
|
||||
valuePlaceHolder = { trunc(dex() + con() * 2) },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Perception",
|
||||
valuePlaceHolder = { trunc(10 + int() * 2) },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Recherche",
|
||||
valuePlaceHolder = { trunc(10 + int() * 2) },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Empathie",
|
||||
valuePlaceHolder = { trunc(cha() + int()) },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Persuasion",
|
||||
valuePlaceHolder = { trunc(cha() * 3) },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Intimidation",
|
||||
valuePlaceHolder = { trunc(cha() + max(pow(), vit()) * 2) },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Baratin",
|
||||
valuePlaceHolder = { trunc(cha() * 2 + int()) },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Marchandage",
|
||||
valuePlaceHolder = { trunc(cha() * 2) },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Discrétion",
|
||||
valuePlaceHolder = { trunc(cha() + dex() * 2 - vit()) },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Escamotage",
|
||||
valuePlaceHolder = { trunc(dex() * 2) },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Premiers soins",
|
||||
valuePlaceHolder = { trunc(int() + dex()) },
|
||||
),
|
||||
),
|
||||
),
|
||||
Group(
|
||||
title = "Occupations",
|
||||
editable = true,
|
||||
fields = emptyList(),
|
||||
),
|
||||
Group(
|
||||
title = "Compétences magiques",
|
||||
editable = true,
|
||||
fields = emptyList(),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun create(sheet: CharacterSheetUio): CharacterSheetEditUio {
|
||||
return CharacterSheetEditUio(
|
||||
name = FieldUio.create(
|
||||
useLabelAsPlaceholder = true,
|
||||
label = "Name",
|
||||
initialValue = sheet.name,
|
||||
),
|
||||
groups = listOf(
|
||||
Group(
|
||||
title = "Charactéristiques",
|
||||
fields = sheet.characteristics.map {
|
||||
FieldUio.create(
|
||||
label = it.label,
|
||||
initialValue = it.value,
|
||||
)
|
||||
},
|
||||
),
|
||||
Group(
|
||||
title = "Charactéristiques dérivées",
|
||||
fields = sheet.subCharacteristics.map {
|
||||
FieldUio.create(
|
||||
label = it.label,
|
||||
initialValue = it.value,
|
||||
)
|
||||
},
|
||||
),
|
||||
Group(
|
||||
title = "Compétances",
|
||||
fields = sheet.skills.map {
|
||||
FieldUio.create(
|
||||
label = it.label,
|
||||
initialValue = "${it.value}",
|
||||
)
|
||||
},
|
||||
),
|
||||
Group(
|
||||
title = "Occupations",
|
||||
editable = true,
|
||||
fields = sheet.occupations.map {
|
||||
FieldUio.create(
|
||||
label = it.label,
|
||||
initialValue = "${it.value}",
|
||||
)
|
||||
},
|
||||
),
|
||||
Group(
|
||||
title = "Compétences magiques",
|
||||
editable = true,
|
||||
fields = sheet.magics.map {
|
||||
FieldUio.create(
|
||||
label = it.label,
|
||||
initialValue = "${it.value}",
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun trunc(value: Int): String {
|
||||
return "${(truncate(value.toFloat() / 5f) * 5f).toInt()}"
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CharacterSheetEdit(
|
||||
form: CharacterSheetEditUio,
|
||||
onSkill: (CharacterSheetEditUio.Group) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(state = rememberScrollState())
|
||||
.padding(all = 24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
|
||||
) {
|
||||
Form(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
field = form.name,
|
||||
)
|
||||
|
||||
form.groups.forEach {
|
||||
DecoratedBox(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = it.title,
|
||||
)
|
||||
it.fields.forEach {
|
||||
Form(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
field = it,
|
||||
)
|
||||
}
|
||||
if (it.editable) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = 8.dp,
|
||||
alignment = Alignment.End
|
||||
)
|
||||
) {
|
||||
Button(
|
||||
colors = ButtonDefaults.textButtonColors(),
|
||||
onClick = { onSkill(it) },
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = "Ajouter une ligne",
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
package com.pixelized.desktop.lwa.screen.characterSheet.edit
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.navigation.LocalScreen
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.FieldUio
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.Form
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
data class CharacterSheetEditPageUio(
|
||||
val id: String,
|
||||
val name: FieldUio,
|
||||
val skills: List<SkillGroup>,
|
||||
) {
|
||||
@Stable
|
||||
data class SkillGroup(
|
||||
val title: String,
|
||||
val type: Type,
|
||||
val editable: Boolean = false,
|
||||
val fields: List<FieldUio>,
|
||||
) {
|
||||
@Stable
|
||||
enum class Type {
|
||||
CHARACTERISTICS,
|
||||
SUB_CHARACTERISTICS,
|
||||
SKILLS,
|
||||
OCCUPATIONS,
|
||||
MAGICS,
|
||||
OTHER,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun CharacterSheetEditPage(
|
||||
viewModel: CharacterSheetEditViewModel = viewModel { CharacterSheetEditViewModel() },
|
||||
) {
|
||||
val screen = LocalScreen.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
CharacterSheetEdit(
|
||||
form = viewModel.characterSheet.value,
|
||||
onSkill = viewModel::onSkill,
|
||||
onCancel = { screen.popBackStack() },
|
||||
onSave = {
|
||||
scope.launch {
|
||||
viewModel.save()
|
||||
screen.popBackStack()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CharacterSheetEdit(
|
||||
form: CharacterSheetEditPageUio,
|
||||
onSkill: (CharacterSheetEditPageUio.SkillGroup) -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
onSave: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(state = rememberScrollState())
|
||||
.padding(all = 24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
|
||||
) {
|
||||
Form(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
field = form.name,
|
||||
)
|
||||
|
||||
form.skills.forEach {
|
||||
DecoratedBox(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = it.title,
|
||||
)
|
||||
it.fields.forEach {
|
||||
Form(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
field = it,
|
||||
)
|
||||
}
|
||||
if (it.editable) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = 8.dp,
|
||||
alignment = Alignment.End
|
||||
)
|
||||
) {
|
||||
Button(
|
||||
colors = ButtonDefaults.textButtonColors(),
|
||||
onClick = { onSkill(it) },
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = "Ajouter une ligne",
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
TextButton(
|
||||
onClick = onCancel,
|
||||
) {
|
||||
Text(text = "Annuler")
|
||||
}
|
||||
TextButton(
|
||||
onClick = onSave,
|
||||
) {
|
||||
Text(text = "Sauvegarder")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
package com.pixelized.desktop.lwa.screen.characterSheet.edit
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.desktop.lwa.business.SkillNormalizerUseCase.normalize
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEditPageUio.SkillGroup
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.FieldUio
|
||||
import java.util.UUID
|
||||
import kotlin.math.max
|
||||
|
||||
class CharacterSheetEditViewModel : ViewModel() {
|
||||
|
||||
private val repository = CharacterSheetRepository
|
||||
private val factory = CharacterSheetFactory()
|
||||
|
||||
private val _characterSheet: MutableState<CharacterSheetEditPageUio>
|
||||
val characterSheet: State<CharacterSheetEditPageUio> get() = _characterSheet
|
||||
|
||||
init {
|
||||
_characterSheet = mutableStateOf(createEmptyCharacterSheet())
|
||||
}
|
||||
|
||||
fun onSkill(skill: SkillGroup) {
|
||||
val sheet = _characterSheet.value
|
||||
|
||||
_characterSheet.value = sheet.copy(
|
||||
skills = sheet.skills.map { group ->
|
||||
if (skill.title == group.title) {
|
||||
group.copy(
|
||||
fields = mutableListOf<FieldUio>().apply {
|
||||
addAll(group.fields)
|
||||
add(
|
||||
FieldUio.create(
|
||||
label = "",
|
||||
isLabelEditable = true,
|
||||
valuePlaceHolder = {
|
||||
when (group.type) {
|
||||
SkillGroup.Type.CHARACTERISTICS -> ""
|
||||
SkillGroup.Type.SUB_CHARACTERISTICS -> ""
|
||||
SkillGroup.Type.SKILLS -> "0"
|
||||
SkillGroup.Type.OCCUPATIONS -> "40"
|
||||
SkillGroup.Type.MAGICS -> "0"
|
||||
SkillGroup.Type.OTHER -> ""
|
||||
}
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
group
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun save() {
|
||||
val sheet = _characterSheet.value
|
||||
val model = factory.convertToModel(sheet = sheet)
|
||||
repository.save(characterSheet = model)
|
||||
}
|
||||
|
||||
private fun createEmptyCharacterSheet(): CharacterSheetEditPageUio {
|
||||
val str = FieldUio.create(label = "Force", valuePlaceHolder = { "0" })
|
||||
val dex = FieldUio.create(label = "Dextérité", valuePlaceHolder = { "0" })
|
||||
val con = FieldUio.create(label = "Constitution", valuePlaceHolder = { "0" })
|
||||
val vit = FieldUio.create(label = "Taille", valuePlaceHolder = { "0" })
|
||||
val int = FieldUio.create(label = "Intelligence", valuePlaceHolder = { "0" })
|
||||
val pow = FieldUio.create(label = "Pouvoir", valuePlaceHolder = { "0" })
|
||||
val cha = FieldUio.create(label = "Charisme", valuePlaceHolder = { "0" })
|
||||
|
||||
fun str(): Int = str.value.value.toIntOrNull() ?: 0
|
||||
fun dex(): Int = dex.value.value.toIntOrNull() ?: 0
|
||||
fun con(): Int = con.value.value.toIntOrNull() ?: 0
|
||||
fun vit(): Int = vit.value.value.toIntOrNull() ?: 0
|
||||
fun int(): Int = int.value.value.toIntOrNull() ?: 0
|
||||
fun pow(): Int = pow.value.value.toIntOrNull() ?: 0
|
||||
fun cha(): Int = cha.value.value.toIntOrNull() ?: 0
|
||||
|
||||
return CharacterSheetEditPageUio(
|
||||
id = UUID.randomUUID().toString(),
|
||||
name = FieldUio.create(
|
||||
useLabelAsPlaceholder = true,
|
||||
label = "Name",
|
||||
),
|
||||
skills = listOf(
|
||||
SkillGroup(
|
||||
title = "Charactéristiques",
|
||||
type = SkillGroup.Type.CHARACTERISTICS,
|
||||
fields = listOf(str, dex, con, vit, int, pow, cha),
|
||||
),
|
||||
SkillGroup(
|
||||
title = "Charactéristiques dérivées",
|
||||
type = SkillGroup.Type.SUB_CHARACTERISTICS,
|
||||
fields = listOf(
|
||||
FieldUio.create(
|
||||
label = "Déplacement",
|
||||
valuePlaceHolder = { "10" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Points de vie",
|
||||
valuePlaceHolder = { "${(con() + vit()) / 2}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Points de pouvoir",
|
||||
valuePlaceHolder = { "${pow()}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Bonus aux dégats",
|
||||
valuePlaceHolder = {
|
||||
val bonus = str() + vit()
|
||||
when {
|
||||
bonus < 12 -> "-1d6"
|
||||
bonus in 12..17 -> "-1d4"
|
||||
bonus in 18..22 -> "-0"
|
||||
bonus in 23..29 -> "1d4"
|
||||
bonus in 30..39 -> "1d6"
|
||||
else -> "2d6"
|
||||
}
|
||||
},
|
||||
),
|
||||
FieldUio.create(label = "Armure", valuePlaceHolder = { "0" }),
|
||||
),
|
||||
),
|
||||
SkillGroup(
|
||||
title = "Compétances",
|
||||
type = SkillGroup.Type.SKILLS,
|
||||
editable = true,
|
||||
fields = listOf(
|
||||
FieldUio.create(
|
||||
label = "Bagarre",
|
||||
valuePlaceHolder = { "${normalize(dex() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Esquive",
|
||||
valuePlaceHolder = { "${normalize(dex() * 2)}" }
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Saisie",
|
||||
valuePlaceHolder = { "${normalize(str() + vit())}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Lancer",
|
||||
valuePlaceHolder = { "${normalize(str() + dex())}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Athlétisme",
|
||||
valuePlaceHolder = { "${normalize(str() + con() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Acrobatie",
|
||||
valuePlaceHolder = { "${normalize(dex() + con() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Perception",
|
||||
valuePlaceHolder = { "${normalize(10 + int() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Recherche",
|
||||
valuePlaceHolder = { "${normalize(10 + int() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Empathie",
|
||||
valuePlaceHolder = { "${normalize(cha() + int())}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Persuasion",
|
||||
valuePlaceHolder = { "${normalize(cha() * 3)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Intimidation",
|
||||
valuePlaceHolder = {
|
||||
"${
|
||||
normalize(
|
||||
cha() + max(
|
||||
pow(),
|
||||
vit()
|
||||
) * 2
|
||||
)
|
||||
}"
|
||||
},
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Baratin",
|
||||
valuePlaceHolder = { "${normalize(cha() * 2 + int())}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Marchandage",
|
||||
valuePlaceHolder = { "${normalize(cha() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Discrétion",
|
||||
valuePlaceHolder = { "${normalize(cha() + dex() * 2 - vit())}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Escamotage",
|
||||
valuePlaceHolder = { "${normalize(dex() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Premiers soins",
|
||||
valuePlaceHolder = { "${normalize(int() + dex())}" },
|
||||
),
|
||||
),
|
||||
),
|
||||
SkillGroup(
|
||||
title = "Occupations",
|
||||
type = SkillGroup.Type.OCCUPATIONS,
|
||||
editable = true,
|
||||
fields = emptyList(),
|
||||
),
|
||||
SkillGroup(
|
||||
title = "Compétences magiques",
|
||||
type = SkillGroup.Type.MAGICS,
|
||||
editable = true,
|
||||
fields = emptyList(),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.pixelized.desktop.lwa.screen.characterSheet.edit
|
||||
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.FieldUio
|
||||
|
||||
class CharacterSheetFactory {
|
||||
|
||||
fun convertToModel(sheet: CharacterSheetEditPageUio): CharacterSheet {
|
||||
return CharacterSheet(
|
||||
id = sheet.id,
|
||||
name = sheet.name.value.value,
|
||||
strength = sheet.skills[0].fields[0].unpack(),
|
||||
dexterity = sheet.skills[0].fields[1].unpack(),
|
||||
constitution = sheet.skills[0].fields[2].unpack(),
|
||||
height = sheet.skills[0].fields[3].unpack(),
|
||||
intelligence = sheet.skills[0].fields[4].unpack(),
|
||||
power = sheet.skills[0].fields[5].unpack(),
|
||||
charisma = sheet.skills[0].fields[6].unpack(),
|
||||
movement = sheet.skills[1].fields[0].unpack(),
|
||||
currentHp = sheet.skills[1].fields[1].unpack(),
|
||||
maxHp = sheet.skills[1].fields[1].unpack(),
|
||||
currentPP = sheet.skills[1].fields[2].unpack(),
|
||||
maxPP = sheet.skills[1].fields[2].unpack(),
|
||||
damageBonus = sheet.skills[1].fields[3].unpack(),
|
||||
armor = sheet.skills[1].fields[4].unpack(),
|
||||
skills = sheet.skills[2].fields.map {
|
||||
CharacterSheet.Skill(
|
||||
label = it.label.value,
|
||||
value = it.value.value.toIntOrNull() ?: 0,
|
||||
used = false,
|
||||
)
|
||||
},
|
||||
occupations = sheet.skills[3].fields.map {
|
||||
CharacterSheet.Skill(
|
||||
label = it.label.value,
|
||||
value = it.value.value.toIntOrNull() ?: 0,
|
||||
used = false,
|
||||
)
|
||||
},
|
||||
magics = sheet.skills[4].fields.map {
|
||||
CharacterSheet.Skill(
|
||||
label = it.label.value,
|
||||
value = it.value.value.toIntOrNull() ?: 0,
|
||||
used = false,
|
||||
)
|
||||
},
|
||||
attacks = emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun <reified T> FieldUio.unpack(): T {
|
||||
val tmp = value.value.ifBlank { valuePlaceHolder.value }
|
||||
return when (T::class) {
|
||||
Int::class -> (tmp.toIntOrNull() ?: 0) as T
|
||||
else -> tmp as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.desktop.lwa.screen.characterSheet.edit
|
||||
package com.pixelized.desktop.lwa.screen.characterSheet.edit.composable
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.fadeIn
|
||||
|
|
@ -6,8 +6,10 @@ import androidx.compose.animation.fadeOut
|
|||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -19,11 +21,13 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Stable
|
||||
open class FieldUio(
|
||||
val useLabelAsPlaceholder: Boolean,
|
||||
val isLabelEditable: Boolean,
|
||||
val label: State<String>,
|
||||
val onLabelChange: (String) -> Unit,
|
||||
val valuePlaceHolder: State<String>,
|
||||
|
|
@ -34,6 +38,7 @@ open class FieldUio(
|
|||
@Stable
|
||||
fun create(
|
||||
useLabelAsPlaceholder: Boolean = false,
|
||||
isLabelEditable: Boolean = false,
|
||||
label: String = "",
|
||||
initialValue: String = "",
|
||||
valuePlaceHolder: () -> String = { "" },
|
||||
|
|
@ -42,6 +47,7 @@ open class FieldUio(
|
|||
val valueState = mutableStateOf(initialValue)
|
||||
return FieldUio(
|
||||
useLabelAsPlaceholder = useLabelAsPlaceholder,
|
||||
isLabelEditable = useLabelAsPlaceholder.not() && isLabelEditable,
|
||||
label = labelState,
|
||||
onLabelChange = { labelState.value = it },
|
||||
valuePlaceHolder = derivedStateOf(valuePlaceHolder),
|
||||
|
|
@ -81,14 +87,29 @@ fun Form(
|
|||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
TextField(
|
||||
AnimatedContent(
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
value = field.label.value,
|
||||
placeholder = { Text(text = "Nom") },
|
||||
singleLine = true,
|
||||
keyboardActions = KeyboardActions { focus.moveFocus(FocusDirection.Next) },
|
||||
onValueChange = field.onLabelChange,
|
||||
)
|
||||
targetState = field.isLabelEditable,
|
||||
transitionSpec = { fadeIn() togetherWith fadeOut() },
|
||||
) { editable ->
|
||||
when (editable) {
|
||||
true -> TextField(
|
||||
value = field.label.value,
|
||||
placeholder = { Text(text = "Nom") },
|
||||
singleLine = true,
|
||||
keyboardActions = KeyboardActions { focus.moveFocus(FocusDirection.Next) },
|
||||
onValueChange = field.onLabelChange,
|
||||
)
|
||||
|
||||
else -> Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.body1,
|
||||
text = field.label.value
|
||||
)
|
||||
}
|
||||
}
|
||||
TextField(
|
||||
modifier = Modifier.width(width = 80.dp),
|
||||
value = field.value.value,
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
package com.pixelized.desktop.lwa.screen.main
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.navigation.LocalScreen
|
||||
import com.pixelized.desktop.lwa.navigation.destination.navigateToCharacterSheetEdit
|
||||
|
||||
@Stable
|
||||
data class CharacterUio(
|
||||
val id: String,
|
||||
val name: String,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun MainPage(
|
||||
viewModel: MainPageViewModel = viewModel { MainPageViewModel() },
|
||||
) {
|
||||
val screen = LocalScreen.current
|
||||
|
||||
Surface {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
MainPageContent(
|
||||
characters = viewModel.characters,
|
||||
onCharacter = {
|
||||
|
||||
},
|
||||
onCharacterEdit = {
|
||||
|
||||
},
|
||||
onCharacterDelete = {
|
||||
|
||||
},
|
||||
onCreateCharacter = {
|
||||
screen.navigateToCharacterSheetEdit()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainPageContent(
|
||||
modifier: Modifier = Modifier,
|
||||
characters: State<List<CharacterUio>>,
|
||||
onCharacter: (CharacterUio) -> Unit,
|
||||
onCharacterEdit: (CharacterUio) -> Unit,
|
||||
onCharacterDelete: (CharacterUio) -> Unit,
|
||||
onCreateCharacter: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
DecoratedBox {
|
||||
Column {
|
||||
characters.value.forEach { sheet ->
|
||||
Row {
|
||||
TextButton(
|
||||
onClick = { onCharacter(sheet) },
|
||||
) {
|
||||
Text(text = sheet.name)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { onCharacterEdit(sheet) },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Edit,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { onCharacterDelete(sheet) },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
TextButton(
|
||||
onClick = { onCreateCharacter() },
|
||||
) {
|
||||
Text(text = "Créer une feuille de personnage")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.pixelized.desktop.lwa.screen.main
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.utils.extention.collectAsState
|
||||
|
||||
class MainPageViewModel : ViewModel() {
|
||||
// using a variable to help with later injection.
|
||||
private val characterSheetRepository = CharacterSheetRepository
|
||||
|
||||
val characters: State<List<CharacterUio>>
|
||||
@Composable
|
||||
@Stable
|
||||
get() {
|
||||
return characterSheetRepository.characterSheet().collectAsState { sheets ->
|
||||
sheets.map { sheet ->
|
||||
CharacterUio(
|
||||
id = sheet.id,
|
||||
name = sheet.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package com.pixelized.desktop.lwa.screen.overlay
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class BlurOverlayViewModel : ViewModel() {
|
||||
private val _overlay = mutableStateOf(false)
|
||||
|
||||
val overlay: State<Boolean>
|
||||
get() = _overlay
|
||||
|
||||
val blur: State<Dp>
|
||||
@Composable
|
||||
get() = animateDpAsState(
|
||||
targetValue = when (overlay.value) {
|
||||
true -> 16.dp
|
||||
else -> 0.dp
|
||||
}
|
||||
)
|
||||
|
||||
fun show() {
|
||||
_overlay.value = true
|
||||
}
|
||||
|
||||
fun hide() {
|
||||
_overlay.value = false
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.desktop.lwa.business.RollUseCase
|
||||
import com.pixelized.desktop.lwa.business.SkillStepUseCase
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetUio
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetPageUio
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -26,7 +26,7 @@ class RollViewModel : ViewModel() {
|
|||
private val _result = mutableStateOf<RollResultUio?>(null)
|
||||
val result: State<RollResultUio?> get() = _result
|
||||
|
||||
fun prepareRoll(node: CharacterSheetUio.Node) {
|
||||
fun prepareRoll(node: CharacterSheetPageUio.Node) {
|
||||
val step = SkillStepUseCase.computeSkillStep(
|
||||
skill = node.value,
|
||||
)
|
||||
|
|
@ -36,7 +36,7 @@ class RollViewModel : ViewModel() {
|
|||
)
|
||||
}
|
||||
|
||||
fun prepareRoll(characteristic: CharacterSheetUio.Characteristic) {
|
||||
fun prepareRoll(characteristic: CharacterSheetPageUio.Characteristic) {
|
||||
val step = SkillStepUseCase.computeSkillStep(
|
||||
skill = (characteristic.value.toIntOrNull() ?: 0) * 5
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue