From 65aa53890f1ccfa0ebf70ec2cc16ffe2b884c049 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Mon, 4 Nov 2024 23:33:11 +0100 Subject: [PATCH] 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. --- composeApp/build.gradle.kts | 16 +- composeApp/characterssheet.preferences_pb | Bin 0 -> 2441 bytes .../desktop/lwa/business/RollUseCase.kt | 2 - .../lwa/business/SkillNormalizerUseCase.kt | 10 + .../desktop/lwa/business/SkillStepUseCase.kt | 2 +- .../pixelized/desktop/lwa/createDataStore.kt | 10 + .../characterSheet/CharacterSheet.kt | 44 +++ .../CharacterSheetPreference.kt | 35 ++ .../CharacterSheetRepository.kt | 52 +++ .../lwa/utils/extention/ByteArrayExt.kt | 27 ++ .../desktop/lwa/utils/extention/FlowExt.kt | 34 ++ .../desktop/lwa/business/RollUseCaseTest.kt | 44 +++ .../business/SkillNormalizerUseCaseText.kt | 29 ++ .../lwa/business/SkillStepUseCaseTest.kt | 180 ++++++++++ .../kotlin/com/pixelized/desktop/lwa/App.kt | 266 +++++++-------- .../{ => decoratedBox}/DecoratedBox.kt | 2 +- .../overlay/BlurOverlay.kt | 20 +- .../overlay/BlurOverlayViewModel.kt | 50 +++ .../kotlin/com/pixelized/desktop/lwa/main.kt | 20 +- .../desktop/lwa/navigation/MainNavHost.kt | 34 ++ .../CharacterSheetEditDestination.kt | 27 ++ .../navigation/destination/MainDestination.kt | 26 ++ ...haracterSheet.kt => CharacterSheetPage.kt} | 21 +- .../characterSheet/CharacterSheetViewModel.kt | 6 +- .../characterSheet/edit/CharacterSheetEdit.kt | 323 ------------------ .../edit/CharacterSheetEditPage.kt | 166 +++++++++ .../edit/CharacterSheetEditViewModel.kt | 223 ++++++++++++ .../edit/CharacterSheetFactory.kt | 58 ++++ .../edit/{ => composable}/FieldUio.kt | 37 +- .../desktop/lwa/screen/main/MainPage.kt | 108 ++++++ .../lwa/screen/main/MainPageViewModel.kt | 29 ++ .../screen/overlay/BlurOverlayViewModel.kt | 33 -- .../desktop/lwa/screen/roll/RollViewModel.kt | 6 +- gradle/libs.versions.toml | 13 +- 34 files changed, 1412 insertions(+), 541 deletions(-) create mode 100644 composeApp/characterssheet.preferences_pb rename composeApp/src/{desktopMain => commonMain}/kotlin/com/pixelized/desktop/lwa/business/RollUseCase.kt (91%) create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/business/SkillNormalizerUseCase.kt rename composeApp/src/{desktopMain => commonMain}/kotlin/com/pixelized/desktop/lwa/business/SkillStepUseCase.kt (98%) create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/createDataStore.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheet.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetPreference.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/ByteArrayExt.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/FlowExt.kt create mode 100644 composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/business/RollUseCaseTest.kt create mode 100644 composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/business/SkillNormalizerUseCaseText.kt create mode 100644 composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/business/SkillStepUseCaseTest.kt rename composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/composable/{ => decoratedBox}/DecoratedBox.kt (92%) rename composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/{screen => composable}/overlay/BlurOverlay.kt (71%) create mode 100644 composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/composable/overlay/BlurOverlayViewModel.kt create mode 100644 composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/navigation/MainNavHost.kt create mode 100644 composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/CharacterSheetEditDestination.kt create mode 100644 composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/MainDestination.kt rename composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/{CharacterSheet.kt => CharacterSheetPage.kt} (93%) delete mode 100644 composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEdit.kt create mode 100644 composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt create mode 100644 composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditViewModel.kt create mode 100644 composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetFactory.kt rename composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/{ => composable}/FieldUio.kt (69%) create mode 100644 composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt create mode 100644 composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPageViewModel.kt delete mode 100644 composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/overlay/BlurOverlayViewModel.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 2f028c0..7a99b4e 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -8,10 +8,10 @@ plugins { kotlin { jvm("desktop") - + sourceSets { val desktopMain by getting - + commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) @@ -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") } } } diff --git a/composeApp/characterssheet.preferences_pb b/composeApp/characterssheet.preferences_pb new file mode 100644 index 0000000000000000000000000000000000000000..fd03c564f3d8b8de2497f3b2e8c0b36d8535c806 GIT binary patch literal 2441 zcmd#U)zrH3e*z&z{c2!05xko|==Io0?Zr!XV@W(yO17n3t~apOlrFTw+~O zP+Y{|l$@WdSCCnenv+?TnxdDITAW>yU!a#$o~T!pT999yS(0B=sh6CQSd^Gtl3Em; zk(yef=ZwPjFnyj}Y$R&T#31a+z?xW;n_uL~zyZ>dS)7~b$-tAGpI2OxSyBpettSI# za%oXfYF>#)0fHS6;K{(5l3G!cT9jE*>B+#Bk(!yFQR2zKlbKhNnv;{6o|>1O>dC;G zn^*zT2ckiGICAsLz#jBuU@gcmPX$?ATvC*pmtK%1rz}+C`pGIDe16aCLLxU2Bf6J3=VCQ6A#Hrr-VTz*(lZ2%)r!4*C@rp zT-U_N)Lb_y&Cpmk&A`Oe(8AEdEY;i$mlAX!GzyQ_`O1vcuD(2?K zW~OQ8=DNn_sfM~Hsc9CvmgeS`y2dFfDTb+uY3AmsGXaFsbQ)GxX>XO3+xOG4EziX415d>47>~s4BSwf6G@BrG!Dm_wdS+#N7O% zf{gs)R0R#6{L%u=iV_BXNI|F&l384wnpXfR5<4L(KtKrtTX1PnS!OCi z%%+4v&^5Opu_PliRYAisFEckWM-xSwJs`8VBrzRT54THZadOe&mGHU%Yym?FgN$>2 zUS48madBc^a;k!cM`}@0eo ((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 diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/createDataStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/createDataStore.kt new file mode 100644 index 0000000..fb5009c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/createDataStore.kt @@ -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 { + return PreferenceDataStoreFactory.createWithPath(produceFile = { producePath().toPath() }) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheet.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheet.kt new file mode 100644 index 0000000..b172432 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheet.kt @@ -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, + // occupations + val occupations: List, + // magic skill + val magics: List, + // attack + val attacks: List, +) : Serializable { + + data class Skill( + val label: String, + val value: Int, + val used: Boolean, + ) : Serializable + + data class Roll( + val label: String, + val roll: String, + ) : Serializable +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetPreference.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetPreference.kt new file mode 100644 index 0000000..5bdb152 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetPreference.kt @@ -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, +) { + suspend fun save(sheets: List) { + dataStore.edit { + it[characterSheetKey] = sheets.toByteArray() + } + } + + suspend fun load(): List { + return loadFlow().first() + } + + fun loadFlow(): Flow> { + return dataStore.data.map { + it[characterSheetKey]?.fromByteArray>() ?: emptyList() + } + } + + companion object { + private val characterSheetKey = byteArrayPreferencesKey("CharacterSheetsPrefKey") + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt new file mode 100644 index 0000000..4a8e5b0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt @@ -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> { + return preferences.loadFlow() + .stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = emptyList() + ) + } + + fun characterSheet(id: String): StateFlow { + 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) + } +} + diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/ByteArrayExt.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/ByteArrayExt.kt new file mode 100644 index 0000000..93e324c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/ByteArrayExt.kt @@ -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 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 +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/FlowExt.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/FlowExt.kt new file mode 100644 index 0000000..1fc1dbb --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/extention/FlowExt.kt @@ -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 StateFlow.collectAsState( + context: CoroutineContext = EmptyCoroutineContext, + convert: (T) -> R, +): State = collectAsState( + initial = value, + context = context, + convert = convert, +) + +@Composable +fun Flow.collectAsState( + initial: T, + context: CoroutineContext = EmptyCoroutineContext, + convert: (T) -> R, +): State = produceState(convert(initial), this, context) { + if (context == EmptyCoroutineContext) { + collect { value = convert(it) } + } else withContext(context) { + collect { value = convert(it) } + } +} \ No newline at end of file diff --git a/composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/business/RollUseCaseTest.kt b/composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/business/RollUseCaseTest.kt new file mode 100644 index 0000000..486843d --- /dev/null +++ b/composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/business/RollUseCaseTest.kt @@ -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 { + val result = MutableList(100) { 0 } + repeat(count) { + val roll = RollUseCase.rollD100() + result[roll - 1] += 1 + } + return result + } +} \ No newline at end of file diff --git a/composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/business/SkillNormalizerUseCaseText.kt b/composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/business/SkillNormalizerUseCaseText.kt new file mode 100644 index 0000000..0888e0b --- /dev/null +++ b/composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/business/SkillNormalizerUseCaseText.kt @@ -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" + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/business/SkillStepUseCaseTest.kt b/composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/business/SkillStepUseCaseTest.kt new file mode 100644 index 0000000..ad43b1e --- /dev/null +++ b/composeApp/src/commonTest/kotlin/com/pixelized/desktop/lwa/business/SkillStepUseCaseTest.kt @@ -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}" + } + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/App.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/App.kt index af8decf..ab43a65 100644 --- a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/App.kt +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/App.kt @@ -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(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().apply { - addAll(group.fields) - add( - FieldUio.create( - label = "", - valuePlaceHolder = { "40" }, - ) - ) - } - ) - } else { - group - } - } - ) - } - ) - } - } - } + MainNavHost() } } -} \ No newline at end of file +} + +// +// val sheetViewModel = viewModel { CharacterSheetViewModel() } +// val overlayViewModel = viewModel { BlurOverlayViewModel() } +// val rollViewModel = viewModel { RollViewModel() } +// +// val edit = remember { mutableStateOf(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().apply { +// addAll(group.fields) +// add( +// FieldUio.create( +// label = "", +// valuePlaceHolder = { "40" }, +// ) +// ) +// } +// ) +// } else { +// group +// } +// } +// ) +// } +// ) +// } +// } +// } +// } +// } +//} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/composable/DecoratedBox.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/composable/decoratedBox/DecoratedBox.kt similarity index 92% rename from composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/composable/DecoratedBox.kt rename to composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/composable/decoratedBox/DecoratedBox.kt index fa125c4..57b7713 100644 --- a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/composable/DecoratedBox.kt +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/composable/decoratedBox/DecoratedBox.kt @@ -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 diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/overlay/BlurOverlay.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/composable/overlay/BlurOverlay.kt similarity index 71% rename from composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/overlay/BlurOverlay.kt rename to composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/composable/overlay/BlurOverlay.kt index 6c5c0a1..4a1db1f 100644 --- a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/overlay/BlurOverlay.kt +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/composable/overlay/BlurOverlay.kt @@ -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, + val background: State, +) + @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, ) diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/composable/overlay/BlurOverlayViewModel.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/composable/overlay/BlurOverlayViewModel.kt new file mode 100644 index 0000000..03ee6c4 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/composable/overlay/BlurOverlayViewModel.kt @@ -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 + 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 + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt index 665339a..af06c8c 100644 --- a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/main.kt @@ -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() + } } } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/navigation/MainNavHost.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/navigation/MainNavHost.kt new file mode 100644 index 0000000..00ea717 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/navigation/MainNavHost.kt @@ -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 { + 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() + } + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/CharacterSheetEditDestination.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/CharacterSheetEditDestination.kt new file mode 100644 index 0000000..20757f3 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/CharacterSheetEditDestination.kt @@ -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) +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/MainDestination.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/MainDestination.kt new file mode 100644 index 0000000..49c9c51 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/MainDestination.kt @@ -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) +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/CharacterSheet.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/CharacterSheetPage.kt similarity index 93% rename from composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/CharacterSheet.kt rename to composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/CharacterSheetPage.kt index fa67793..66fbce8 100644 --- a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/CharacterSheet.kt +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/CharacterSheetPage.kt @@ -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, val subCharacteristics: List, @@ -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 diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/CharacterSheetViewModel.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/CharacterSheetViewModel.kt index 56f5f5d..0df2958 100644 --- a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/CharacterSheetViewModel.kt +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/CharacterSheetViewModel.kt @@ -6,11 +6,11 @@ import androidx.lifecycle.ViewModel class CharacterSheetViewModel : ViewModel() { - private val _sheet = mutableStateOf(null) - val sheet: State get() = _sheet + private val _sheet = mutableStateOf(null) + val sheet: State get() = _sheet fun showCharacterSheet() { - _sheet.value = CharacterSheetUio.Koryas + _sheet.value = CharacterSheetPageUio.Koryas } fun hideCharacterSheet() { diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEdit.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEdit.kt deleted file mode 100644 index 287e87c..0000000 --- a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEdit.kt +++ /dev/null @@ -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, -) { - @Stable - data class Group( - val title: String, - val editable: Boolean = false, - val fields: List, - ) - - 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, - ) - } - } - } - } - } - } - } - } -} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt new file mode 100644 index 0000000..46862e5 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt @@ -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, +) { + @Stable + data class SkillGroup( + val title: String, + val type: Type, + val editable: Boolean = false, + val fields: List, + ) { + @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") + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditViewModel.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditViewModel.kt new file mode 100644 index 0000000..51ddb98 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditViewModel.kt @@ -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 + val characterSheet: State 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().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(), + ), + ) + ) + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetFactory.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetFactory.kt new file mode 100644 index 0000000..6e44575 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetFactory.kt @@ -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 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 + } + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/FieldUio.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/FieldUio.kt similarity index 69% rename from composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/FieldUio.kt rename to composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/FieldUio.kt index 7a96de6..e03e31b 100644 --- a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/FieldUio.kt +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/composable/FieldUio.kt @@ -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, val onLabelChange: (String) -> Unit, val valuePlaceHolder: State, @@ -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, diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt new file mode 100644 index 0000000..33efb6e --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt @@ -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>, + 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") + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPageViewModel.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPageViewModel.kt new file mode 100644 index 0000000..1936106 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPageViewModel.kt @@ -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> + @Composable + @Stable + get() { + return characterSheetRepository.characterSheet().collectAsState { sheets -> + sheets.map { sheet -> + CharacterUio( + id = sheet.id, + name = sheet.name, + ) + } + } + } + + +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/overlay/BlurOverlayViewModel.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/overlay/BlurOverlayViewModel.kt deleted file mode 100644 index bc7b4dc..0000000 --- a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/overlay/BlurOverlayViewModel.kt +++ /dev/null @@ -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 - get() = _overlay - - val blur: State - @Composable - get() = animateDpAsState( - targetValue = when (overlay.value) { - true -> 16.dp - else -> 0.dp - } - ) - - fun show() { - _overlay.value = true - } - - fun hide() { - _overlay.value = false - } -} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt index b8e2eee..c7d22e5 100644 --- a/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt +++ b/composeApp/src/desktopMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt @@ -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(null) val result: State 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 ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7e51c53..74c55c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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]