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
|
|
@ -21,9 +21,14 @@ kotlin {
|
|||
implementation(compose.components.uiToolingPreview)
|
||||
implementation(libs.androidx.lifecycle.viewmodel)
|
||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||
|
||||
implementation("org.jetbrains.androidx.navigation:navigation-compose:2.8.0-alpha10")
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
}
|
||||
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
|
||||
desktopMain.dependencies {
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(libs.kotlinx.coroutines.swing)
|
||||
|
|
@ -40,6 +45,9 @@ compose.desktop {
|
|||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
||||
packageName = "com.pixelized.desktop.lwa"
|
||||
packageVersion = "1.0.0"
|
||||
|
||||
// Use system theming fot the app toolbars.
|
||||
jvmArgs("-Dapple.awt.application.appearance=system")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
composeApp/characterssheet.preferences_pb
Normal file
BIN
composeApp/characterssheet.preferences_pb
Normal file
Binary file not shown.
|
|
@ -5,10 +5,8 @@ object RollUseCase {
|
|||
|
||||
/**
|
||||
* (Math.random() * 100 + 1).toInt()
|
||||
* TODO : test.
|
||||
*/
|
||||
fun rollD100(): Int {
|
||||
return d100.random()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.pixelized.desktop.lwa.business
|
||||
|
||||
import kotlin.math.truncate
|
||||
|
||||
object SkillNormalizerUseCase {
|
||||
|
||||
fun normalize(value: Int): Int {
|
||||
return (truncate(value.toFloat() / 5f) * 5f).toInt()
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ object SkillStepUseCase {
|
|||
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
|
||||
70 -> ((criticalSuccess?.second ?: 0) + 1) to 14
|
||||
75 -> ((criticalSuccess?.second ?: 0) + 1) to 15
|
||||
80 -> ((criticalSuccess?.second ?: 0) + 1) to 16
|
||||
85 -> ((criticalSuccess?.second ?: 0) + 1) to 17
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.pixelized.desktop.lwa
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import okio.Path.Companion.toPath
|
||||
|
||||
fun createDataStore(producePath: () -> String): DataStore<Preferences> {
|
||||
return PreferenceDataStoreFactory.createWithPath(produceFile = { producePath().toPath() })
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.pixelized.desktop.lwa.repository.characterSheet
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class CharacterSheet(
|
||||
val id: String,
|
||||
val name: String,
|
||||
// characteristics
|
||||
val strength: Int,
|
||||
val dexterity: Int,
|
||||
val constitution: Int,
|
||||
val height: Int,
|
||||
val intelligence: Int,
|
||||
val power: Int,
|
||||
val charisma: Int,
|
||||
// sub characteristics
|
||||
val movement: Int,
|
||||
val currentHp: Int,
|
||||
val maxHp: Int,
|
||||
val currentPP: Int,
|
||||
val maxPP: Int,
|
||||
val damageBonus: String,
|
||||
val armor: Int,
|
||||
// skills
|
||||
val skills: List<Skill>,
|
||||
// occupations
|
||||
val occupations: List<Skill>,
|
||||
// magic skill
|
||||
val magics: List<Skill>,
|
||||
// attack
|
||||
val attacks: List<Roll>,
|
||||
) : Serializable {
|
||||
|
||||
data class Skill(
|
||||
val label: String,
|
||||
val value: Int,
|
||||
val used: Boolean,
|
||||
) : Serializable
|
||||
|
||||
data class Roll(
|
||||
val label: String,
|
||||
val roll: String,
|
||||
) : Serializable
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.pixelized.desktop.lwa.repository.characterSheet
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.byteArrayPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import com.pixelized.desktop.lwa.utils.extention.fromByteArray
|
||||
import com.pixelized.desktop.lwa.utils.extention.toByteArray
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class CharacterSheetPreference(
|
||||
private val dataStore: DataStore<Preferences>,
|
||||
) {
|
||||
suspend fun save(sheets: List<CharacterSheet>) {
|
||||
dataStore.edit {
|
||||
it[characterSheetKey] = sheets.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun load(): List<CharacterSheet> {
|
||||
return loadFlow().first()
|
||||
}
|
||||
|
||||
fun loadFlow(): Flow<List<CharacterSheet>> {
|
||||
return dataStore.data.map {
|
||||
it[characterSheetKey]?.fromByteArray<List<CharacterSheet>>() ?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val characterSheetKey = byteArrayPreferencesKey("CharacterSheetsPrefKey")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.pixelized.desktop.lwa.repository.characterSheet
|
||||
|
||||
import com.pixelized.desktop.lwa.createDataStore
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
object CharacterSheetRepository {
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
private val preferences = CharacterSheetPreference(
|
||||
dataStore = createDataStore { "characterssheet.preferences_pb" }
|
||||
)
|
||||
|
||||
fun characterSheet(): StateFlow<List<CharacterSheet>> {
|
||||
return preferences.loadFlow()
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
fun characterSheet(id: String): StateFlow<CharacterSheet?> {
|
||||
return preferences.loadFlow()
|
||||
.map { sheets ->
|
||||
sheets.firstOrNull { sheet -> sheet.id == id }
|
||||
}
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = null
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun save(characterSheet: CharacterSheet) {
|
||||
val savedSheets = preferences.load().toMutableList()
|
||||
val savedIndex = savedSheets.indexOfFirst { it.id == characterSheet.id }
|
||||
if (savedIndex >= 0) {
|
||||
// this sheet is already saved. update it
|
||||
savedSheets[savedIndex] = characterSheet
|
||||
} else {
|
||||
// add the character sheet to the list.
|
||||
savedSheets.add(characterSheet)
|
||||
}
|
||||
// save the list of characters sheet.
|
||||
preferences.save(sheets = savedSheets)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.pixelized.desktop.lwa.utils.extention
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> ByteArray.fromByteArray(): T {
|
||||
val byteArrayInputStream = ByteArrayInputStream(this)
|
||||
val objectInput = ObjectInputStream(byteArrayInputStream)
|
||||
val result = objectInput.readObject() as T
|
||||
objectInput.close()
|
||||
byteArrayInputStream.close()
|
||||
return result
|
||||
}
|
||||
|
||||
fun Any.toByteArray(): ByteArray {
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
val objectOutputStream = ObjectOutputStream(byteArrayOutputStream)
|
||||
objectOutputStream.writeObject(this)
|
||||
objectOutputStream.flush()
|
||||
val result = byteArrayOutputStream.toByteArray()
|
||||
byteArrayOutputStream.close()
|
||||
objectOutputStream.close()
|
||||
return result
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.pixelized.desktop.lwa.utils.extention
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.produceState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
@Suppress("StateFlowValueCalledInComposition")
|
||||
@Composable
|
||||
fun <T, R> StateFlow<T>.collectAsState(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
convert: (T) -> R,
|
||||
): State<R> = collectAsState(
|
||||
initial = value,
|
||||
context = context,
|
||||
convert = convert,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun <T, R> Flow<T>.collectAsState(
|
||||
initial: T,
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
convert: (T) -> R,
|
||||
): State<R> = produceState(convert(initial), this, context) {
|
||||
if (context == EmptyCoroutineContext) {
|
||||
collect { value = convert(it) }
|
||||
} else withContext(context) {
|
||||
collect { value = convert(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.pixelized.desktop.lwa.business
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
class RollUseCaseTest {
|
||||
|
||||
companion object {
|
||||
private const val ROLL_COUNT = 1000000000
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRoll1D100() {
|
||||
val result = build1D100ResultSet()
|
||||
|
||||
println("Testing if with $ROLL_COUNT rolls we have at least 1 result of each value.")
|
||||
assert(result.all { it > 0 }) {
|
||||
"Maybe a false negative, we expected that all values should have a result.\nroll amount: $ROLL_COUNT - result:$result"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRoll1D100_() {
|
||||
val result = build1D100ResultSet()
|
||||
|
||||
val delta = 0.001f
|
||||
val median = (ROLL_COUNT / 100).let {
|
||||
(it * (1f - delta)).toInt()..(it * (1f + delta)).toInt()
|
||||
}
|
||||
|
||||
println("Testing if with $ROLL_COUNT rolls we have at least all results in $median.")
|
||||
assert(result.all { it in median }) {
|
||||
"Maybe a false negative, we expected that all values should be in $median a result.\nroll amount: $ROLL_COUNT - result:$result"
|
||||
}
|
||||
}
|
||||
|
||||
private fun build1D100ResultSet(count: Int = ROLL_COUNT): List<Int> {
|
||||
val result = MutableList(100) { 0 }
|
||||
repeat(count) {
|
||||
val roll = RollUseCase.rollD100()
|
||||
result[roll - 1] += 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.pixelized.desktop.lwa.business
|
||||
|
||||
import com.pixelized.desktop.lwa.business.SkillNormalizerUseCase.normalize
|
||||
import org.junit.Test
|
||||
|
||||
class SkillNormalizerUseCaseText {
|
||||
|
||||
@Test
|
||||
fun testNormalization() {
|
||||
val samples = listOf(
|
||||
0 to 0,
|
||||
1 to 0,
|
||||
2 to 0,
|
||||
3 to 0,
|
||||
4 to 0,
|
||||
5 to 5,
|
||||
6 to 5,
|
||||
7 to 5,
|
||||
8 to 5,
|
||||
9 to 5,
|
||||
10 to 10,
|
||||
)
|
||||
samples.forEach { (value, expected) ->
|
||||
assert(normalize(value) == expected) {
|
||||
"$value should be normalized to $expected"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
package com.pixelized.desktop.lwa.business
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
class SkillStepUseCaseTest {
|
||||
|
||||
companion object {
|
||||
val EXPECTED = hashMapOf(
|
||||
0 to listOf(-1..-1, 1..1, 2..5, 6..95, 96..100),
|
||||
5 to listOf(-1..-1, 1..1, 2..5, 6..95, 96..100),
|
||||
10 to listOf(1..1, 2..2, 3..10, 11..95, 96..100),
|
||||
15 to listOf(1..1, 2..3, 4..15, 16..96, 97..100),
|
||||
20 to listOf(1..1, 2..4, 5..20, 21..96, 97..100),
|
||||
25 to listOf(1..1, 2..5, 6..25, 26..96, 97..100),
|
||||
30 to listOf(1..2, 3..6, 7..30, 31..96, 97..100),
|
||||
35 to listOf(1..2, 3..7, 8..35, 36..97, 98..100),
|
||||
40 to listOf(1..2, 3..8, 9..40, 41..97, 98..100),
|
||||
45 to listOf(1..2, 3..9, 10..45, 46..97, 98..100),
|
||||
50 to listOf(1..3, 4..10, 11..50, 51..97, 98..100),
|
||||
55 to listOf(1..3, 4..11, 12..55, 56..98, 99..100),
|
||||
60 to listOf(1..3, 4..12, 13..60, 61..98, 99..100),
|
||||
65 to listOf(1..3, 4..13, 14..65, 66..98, 99..100),
|
||||
70 to listOf(1..4, 5..14, 15..70, 71..98, 99..100),
|
||||
75 to listOf(1..4, 5..15, 16..75, 76..99, 100..100),
|
||||
80 to listOf(1..4, 5..16, 17..80, 81..99, 100..100),
|
||||
85 to listOf(1..4, 5..17, 18..85, 86..99, 100..100),
|
||||
90 to listOf(1..5, 6..18, 19..90, 91..99, 100..100),
|
||||
95 to listOf(1..5, 6..19, 20..95, 96..99, 100..100),
|
||||
100 to listOf(1..5, 6..20, 21..99, -1..-1, 100..100),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt_0() {
|
||||
testStepForSkillStep(skill = 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt_5() {
|
||||
testStepForSkillStep(skill = 5)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt10() {
|
||||
testStepForSkillStep(skill = 10)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt15() {
|
||||
testStepForSkillStep(skill = 15)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt20() {
|
||||
testStepForSkillStep(skill = 20)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt25() {
|
||||
testStepForSkillStep(skill = 25)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt30() {
|
||||
testStepForSkillStep(skill = 30)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt35() {
|
||||
testStepForSkillStep(skill = 35)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt40() {
|
||||
testStepForSkillStep(skill = 40)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt45() {
|
||||
testStepForSkillStep(skill = 45)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt50() {
|
||||
testStepForSkillStep(skill = 50)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt55() {
|
||||
testStepForSkillStep(skill = 55)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt60() {
|
||||
testStepForSkillStep(skill = 60)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt65() {
|
||||
testStepForSkillStep(skill = 65)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt70() {
|
||||
testStepForSkillStep(skill = 70)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt75() {
|
||||
testStepForSkillStep(skill = 75)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt80() {
|
||||
testStepForSkillStep(skill = 80)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt85() {
|
||||
testStepForSkillStep(skill = 85)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt90() {
|
||||
testStepForSkillStep(skill = 90)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt95() {
|
||||
testStepForSkillStep(skill = 95)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAt100() {
|
||||
testStepForSkillStep(skill = 100)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStepForSkillAbove100() {
|
||||
val levels = List(50) { 100 + it * 5 }
|
||||
|
||||
levels.forEach { skillLevel ->
|
||||
testStepForSkillStep(
|
||||
skill = skillLevel,
|
||||
expectedCriticalSuccessRange = 1..(skillLevel * 5 / 100),
|
||||
expectedSpecialSuccessRange = (skillLevel * 5 / 100 + 1)..(skillLevel * 20 / 100),
|
||||
expectedSuccessRange = (skillLevel * 20 / 100 + 1)..99,
|
||||
expectedFailureRange = -1..-1,
|
||||
expectedCriticalFailureRange = 100..100,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun testStepForSkillStep(
|
||||
skill: Int,
|
||||
expectedCriticalSuccessRange: IntRange = EXPECTED[skill]!![0],
|
||||
expectedSpecialSuccessRange: IntRange = EXPECTED[skill]!![1],
|
||||
expectedSuccessRange: IntRange = EXPECTED[skill]!![2],
|
||||
expectedFailureRange: IntRange = EXPECTED[skill]!![3],
|
||||
expectedCriticalFailureRange: IntRange = EXPECTED[skill]!![4],
|
||||
) {
|
||||
val step = SkillStepUseCase.computeSkillStep(skill = skill)
|
||||
|
||||
assert(step.criticalSuccessRange == expectedCriticalSuccessRange) {
|
||||
"Skill level $skill - Critical success range should be $expectedCriticalSuccessRange bu was ${step.criticalSuccessRange}"
|
||||
}
|
||||
assert(step.specialSuccessRange == expectedSpecialSuccessRange) {
|
||||
"Skill level $skill - Special success range should be $expectedSpecialSuccessRange bu was ${step.specialSuccessRange}"
|
||||
}
|
||||
assert(step.successRange == expectedSuccessRange) {
|
||||
"Skill level $skill - Success range should be $expectedSuccessRange bu was ${step.successRange}"
|
||||
}
|
||||
assert(step.failureRange == expectedFailureRange) {
|
||||
"Skill level $skill - failure range should be $expectedFailureRange bu was ${step.failureRange}"
|
||||
}
|
||||
assert(step.criticalFailureRange == expectedCriticalFailureRange) {
|
||||
"Skill level $skill - Critical failure range should be $expectedCriticalFailureRange bu was ${step.criticalFailureRange}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,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 {
|
||||
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),
|
||||
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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
[versions]
|
||||
androidx-lifecycle = "2.8.3"
|
||||
compose-multiplatform = "1.7.0"
|
||||
junit = "4.13.2"
|
||||
kotlin = "2.0.21"
|
||||
kotlinx-coroutines = "1.9.0"
|
||||
junit = "4.13.2"
|
||||
compose-multiplatform = "1.7.0"
|
||||
androidx-lifecycle = "2.8.3"
|
||||
androidx-navigation = "2.8.0-alpha10"
|
||||
androidx-datastore = "1.1.1"
|
||||
|
||||
[libraries]
|
||||
# Test
|
||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
|
||||
# Compose
|
||||
androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
|
||||
androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
|
||||
androidx-navigation-compose = { group = "org.jetbrains.androidx.navigation", name = "navigation-compose", version.ref = "androidx-navigation" }
|
||||
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences-core", version.ref = "androidx-datastore" }
|
||||
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
||||
|
||||
[plugins]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue