From 880d19eb2023bae1042d007c9fdb66f17ad8792c Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Tue, 19 Sep 2023 21:29:16 +0200 Subject: [PATCH] Add spells to the app. --- app/build.gradle.kts | 28 +- .../ConvertAttackIntoDisplayableFactory.kt | 75 --- .../ConvertCounterIntoDisplayableFactory.kt | 13 - .../rplexicon/model/AssignedSpell.kt | 8 + .../com/pixelized/rplexicon/model/Attack.kt | 8 +- .../rplexicon/model/CharacterSheet.kt | 20 +- .../com/pixelized/rplexicon/model/Spell.kt | 24 + .../com/pixelized/rplexicon/model/Throw.kt | 7 + .../repository/data/ActionRepository.kt | 2 +- .../repository/data/AlterationRepository.kt | 22 +- .../data/CharacterSheetRepository.kt | 2 +- .../repository/data/LexiconRepository.kt | 2 +- .../repository/data/LocationRepository.kt | 4 +- .../repository/data/QuestRepository.kt | 2 +- .../repository/data/SpellRepository.kt | 48 ++ .../parser}/AttackParser.kt | 32 +- .../parser}/CharacterSheetParser.kt | 6 +- .../parser}/GenderParser.kt | 2 +- .../parser}/LexiconParser.kt | 4 +- .../parser}/LocationParser.kt | 4 +- .../parser}/MarqueeParser.kt | 4 +- .../parser}/PortraitParser.kt | 2 +- .../parser}/QuestParser.kt | 4 +- .../model => repository/parser}/RaceParser.kt | 2 +- .../repository/parser/ThrowParser.kt | 26 + .../parser}/alteration/AlterationParser.kt | 8 +- .../parser}/alteration/CounterParser.kt | 2 +- .../parser}/alteration/PropertyParser.kt | 2 +- .../parser}/alteration/StatusParser.kt | 2 +- .../parser}/roll/DiceParser.kt | 2 +- .../parser}/roll/FlatValueParser.kt | 2 +- .../parser}/roll/ModifierParser.kt | 4 +- .../parser/spell/AssignedSpellParser.kt | 67 ++ .../parser/spell/SpellBookParser.kt | 103 +++ .../ui/composable/BackgroundImage.kt | 28 + .../rplexicon/ui/navigation/HomeNavHost.kt | 6 +- .../rplexicon/ui/navigation/ScreenNavHost.kt | 2 + .../screens/ComposableSpellDetail.kt | 61 ++ .../screens/character/CharacterSheetScreen.kt | 345 +++++----- .../character/CharacterSheetViewModel.kt | 620 +----------------- .../ui/screens/character/composable/Action.kt | 126 ---- .../screens/character/composable/Counter.kt | 163 ----- .../{Weapon.kt => actions/AttackItem.kt} | 60 +- .../composable/actions/SpellHeader.kt | 141 ++++ .../character/composable/actions/SpellItem.kt | 260 ++++++++ .../composable/actions/SpellLevelItem.kt | 126 ++++ .../character/CharacterSheetHeader.kt | 64 ++ .../composable/{ => character}/LabelPoint.kt | 2 +- .../{ => character}/MasteryCircle.kt | 2 +- .../composable/{ => character}/Proficiency.kt | 2 +- .../composable/{ => character}/Stat.kt | 2 +- .../preview/rememberAttackListStatePreview.kt | 46 ++ .../rememberCharacterHeaderStatePreview.kt | 32 + .../rememberCharacterSheetStatePreview.kt | 180 +++++ .../preview/rememberSpellLevelChooserState.kt | 45 ++ .../preview/rememberSpellListStatePreview.kt | 76 +++ .../character/factory/AttackUioFactory.kt | 84 +++ .../factory/CharacterSheetHeaderUioFactory.kt | 30 + .../factory/CharacterSheetUioFactory.kt} | 30 +- .../character/factory/SpellUioFactory.kt | 58 ++ .../ui/screens/character/pages/ActionsPage.kt | 38 -- .../character/pages/actions/ActionsPage.kt | 165 +++++ .../pages/actions/AttackActionViewModel.kt | 224 +++++++ .../pages/actions/SpellsActionViewModel.kt | 304 +++++++++ .../pages/chooser/SpellLevelChooser.kt | 75 +++ .../{ => proficiency}/ProficiencyPage.kt | 102 ++- .../pages/proficiency/ProficiencyViewModel.kt | 419 ++++++++++++ .../preview/rememberCharacterSheetPreview.kt | 191 ------ .../preview/rememberWeaponListStatePreview.kt | 37 -- .../screens/lexicon/list/LexiconViewModel.kt | 10 + .../ConvertRollIntoDisplayableFactory.kt | 2 +- .../ui/screens/rolls/RollOverlayViewModel.kt | 1 - .../ui/screens/spell/SpellDetailScreen.kt | 266 ++++++++ .../ui/screens/spell/SpellDetailViewModel.kt | 44 ++ .../rplexicon/utilitary/extentions/IntEx.kt | 7 +- .../extentions/local/AlterationEx.kt | 16 + .../extentions/local/CharacterSheetEx.kt | 45 ++ .../utilitary/extentions/local/PropertyEx.kt | 50 ++ .../utilitary/extentions/local/SchoolEx.kt | 28 + .../extentions/{ => local}/SheetEx.kt | 2 +- .../utilitary/extentions/local/ThrowEx.kt | 20 + .../main/res/drawable/ic_abjuration_24.xml | 9 + .../main/res/drawable/ic_conjuration_24.xml | 9 + .../main/res/drawable/ic_divination_24.xml | 9 + .../main/res/drawable/ic_enchantment_24.xml | 9 + app/src/main/res/drawable/ic_evocation_24.xml | 9 + app/src/main/res/drawable/ic_illusion_24.xml | 9 + .../main/res/drawable/ic_necromancy_24.xml | 9 + .../main/res/drawable/ic_transmutation_24.xml | 9 + app/src/main/res/values-fr/strings.xml | 26 + app/src/main/res/values/strings.xml | 26 + build.gradle.kts | 6 +- 92 files changed, 3713 insertions(+), 1597 deletions(-) delete mode 100644 app/src/main/java/com/pixelized/rplexicon/facotry/displayable/ConvertAttackIntoDisplayableFactory.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/facotry/displayable/ConvertCounterIntoDisplayableFactory.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/model/AssignedSpell.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/model/Spell.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/model/Throw.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/repository/data/SpellRepository.kt rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/AttackParser.kt (69%) rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/CharacterSheetParser.kt (98%) rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/GenderParser.kt (90%) rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/LexiconParser.kt (96%) rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/LocationParser.kt (93%) rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/MarqueeParser.kt (95%) rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/PortraitParser.kt (88%) rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/QuestParser.kt (96%) rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/RaceParser.kt (96%) create mode 100644 app/src/main/java/com/pixelized/rplexicon/repository/parser/ThrowParser.kt rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/alteration/AlterationParser.kt (92%) rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/alteration/CounterParser.kt (97%) rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/alteration/PropertyParser.kt (86%) rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/alteration/StatusParser.kt (96%) rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/roll/DiceParser.kt (93%) rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/roll/FlatValueParser.kt (91%) rename app/src/main/java/com/pixelized/rplexicon/{facotry/model => repository/parser}/roll/ModifierParser.kt (86%) create mode 100644 app/src/main/java/com/pixelized/rplexicon/repository/parser/spell/AssignedSpellParser.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/repository/parser/spell/SpellBookParser.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableSpellDetail.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Action.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Counter.kt rename app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/{Weapon.kt => actions/AttackItem.kt} (75%) create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellHeader.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellItem.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellLevelItem.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/CharacterSheetHeader.kt rename app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/{ => character}/LabelPoint.kt (97%) rename app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/{ => character}/MasteryCircle.kt (95%) rename app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/{ => character}/Proficiency.kt (99%) rename app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/{ => character}/Stat.kt (97%) create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberAttackListStatePreview.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberCharacterHeaderStatePreview.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberCharacterSheetStatePreview.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberSpellLevelChooserState.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberSpellListStatePreview.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/AttackUioFactory.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetHeaderUioFactory.kt rename app/src/main/java/com/pixelized/rplexicon/{facotry/displayable/ConvertCharacterSheetIntoDisplayableFactory.kt => ui/screens/character/factory/CharacterSheetUioFactory.kt} (88%) create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/SpellUioFactory.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/ActionsPage.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ActionsPage.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/AttackActionViewModel.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SpellsActionViewModel.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/chooser/SpellLevelChooser.kt rename app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/{ => proficiency}/ProficiencyPage.kt (62%) create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyViewModel.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/preview/rememberCharacterSheetPreview.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/preview/rememberWeaponListStatePreview.kt rename app/src/main/java/com/pixelized/rplexicon/{facotry/displayable => ui/screens/rolls}/ConvertRollIntoDisplayableFactory.kt (98%) create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailScreen.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailViewModel.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/AlterationEx.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/CharacterSheetEx.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/PropertyEx.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/SchoolEx.kt rename app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/{ => local}/SheetEx.kt (94%) create mode 100644 app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/ThrowEx.kt create mode 100644 app/src/main/res/drawable/ic_abjuration_24.xml create mode 100644 app/src/main/res/drawable/ic_conjuration_24.xml create mode 100644 app/src/main/res/drawable/ic_divination_24.xml create mode 100644 app/src/main/res/drawable/ic_enchantment_24.xml create mode 100644 app/src/main/res/drawable/ic_evocation_24.xml create mode 100644 app/src/main/res/drawable/ic_illusion_24.xml create mode 100644 app/src/main/res/drawable/ic_necromancy_24.xml create mode 100644 app/src/main/res/drawable/ic_transmutation_24.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ba090cc..510e894 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -46,7 +46,7 @@ android { isMinifyEnabled = false signingConfig = signingConfigs.getByName("debug") defaultConfig { - versionCode = 999999 + versionCode = 1 } proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" @@ -80,7 +80,7 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = "1.5.2" + kotlinCompilerExtensionVersion = "1.5.3" } packaging { @@ -95,22 +95,22 @@ android { } dependencies { - implementation("androidx.core:core-ktx:1.10.1") + implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.activity:activity-compose:1.7.2") // Compose - implementation("androidx.compose.ui:ui:1.5.0") - implementation("androidx.compose.ui:ui-util:1.5.0") - implementation("androidx.compose.ui:ui-graphics:1.5.0") - implementation("androidx.compose.ui:ui-tooling-preview:1.5.0") - implementation("androidx.compose.material:material:1.5.0") + implementation("androidx.compose.ui:ui:1.5.1") + implementation("androidx.compose.ui:ui-util:1.5.1") + implementation("androidx.compose.ui:ui-graphics:1.5.1") + implementation("androidx.compose.ui:ui-tooling-preview:1.5.1") + implementation("androidx.compose.material:material:1.5.1") implementation("androidx.compose.material3:material3:1.1.1") - debugImplementation("androidx.compose.ui:ui-tooling:1.5.0") + debugImplementation("androidx.compose.ui:ui-tooling:1.5.1") implementation("androidx.constraintlayout:constraintlayout-compose:1.0.1") // Navigation - implementation("androidx.navigation:navigation-compose:2.7.1") + implementation("androidx.navigation:navigation-compose:2.7.2") // Accompanist implementation("com.google.accompanist:accompanist-placeholder:0.30.1") @@ -119,7 +119,7 @@ dependencies { implementation("androidx.core:core-splashscreen:1.0.1") // Google service - implementation("com.google.android.gms:play-services-auth:20.6.0") + implementation("com.google.android.gms:play-services-auth:20.7.0") implementation( dependencyNotation = "com.google.api-client:google-api-client-android:1.23.0", dependencyConfiguration = { exclude("org.apache.httpcomponents") }, @@ -130,14 +130,14 @@ dependencies { ) // Import the BoM for the Firebase platform - implementation(platform("com.google.firebase:firebase-bom:32.2.0")) + implementation(platform("com.google.firebase:firebase-bom:32.3.1")) implementation("com.google.firebase:firebase-crashlytics-ktx") implementation("com.google.firebase:firebase-analytics-ktx") // Hilt: Dependency injection implementation("androidx.hilt:hilt-navigation-compose:1.0.0") - implementation("com.google.dagger:hilt-android:2.46.1") - kapt("com.google.dagger:hilt-compiler:2.46.1") + implementation("com.google.dagger:hilt-android:2.48") + kapt("com.google.dagger:hilt-compiler:2.48") // Image implementation("com.github.skydoves:landscapist-glide:2.1.11") diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/displayable/ConvertAttackIntoDisplayableFactory.kt b/app/src/main/java/com/pixelized/rplexicon/facotry/displayable/ConvertAttackIntoDisplayableFactory.kt deleted file mode 100644 index 26087eb..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/displayable/ConvertAttackIntoDisplayableFactory.kt +++ /dev/null @@ -1,75 +0,0 @@ -package com.pixelized.rplexicon.facotry.displayable - -import com.pixelized.rplexicon.R -import com.pixelized.rplexicon.model.Attack -import com.pixelized.rplexicon.model.CharacterSheet -import com.pixelized.rplexicon.model.Property -import com.pixelized.rplexicon.ui.screens.character.composable.WeaponUio -import com.pixelized.rplexicon.utilitary.extentions.icon -import com.pixelized.rplexicon.utilitary.extentions.modifier -import javax.inject.Inject - -class ConvertAttackIntoDisplayableFactory @Inject constructor() { - - fun toUio(attack: Attack, characterSheet: CharacterSheet): WeaponUio? { - val hit = attack.hit?.let { dice -> - val modifier = dice.modifier.sumOf { - when (it) { - Property.PROFICIENCY -> characterSheet.proficiency - Property.STRENGTH -> characterSheet.strength.modifier - Property.DEXTERITY -> characterSheet.dexterity.modifier - Property.CONSTITUTION -> characterSheet.constitution.modifier - Property.INTELLIGENCE -> characterSheet.intelligence.modifier - Property.WISDOM -> characterSheet.wisdom.modifier - Property.CHARISMA -> characterSheet.charisma.modifier - else -> 0 - } - } - WeaponUio.Dice( - icon = dice.faces.icon, - label = "${dice.amount}d${dice.faces}${if (modifier > 0) "+$modifier" else ""}", - ) - } - val damage = attack.damage?.let { dice -> - val modifier = dice.modifier.sumOf { - when (it) { - Property.PROFICIENCY -> characterSheet.proficiency - Property.STRENGTH -> characterSheet.strength.modifier - Property.DEXTERITY -> characterSheet.dexterity.modifier - Property.CONSTITUTION -> characterSheet.constitution.modifier - Property.INTELLIGENCE -> characterSheet.intelligence.modifier - Property.WISDOM -> characterSheet.wisdom.modifier - Property.CHARISMA -> characterSheet.charisma.modifier - else -> 0 - } - } - WeaponUio.Dice( - icon = dice.faces.icon, - label = "${dice.amount}d${dice.faces}${if (modifier > 0) "+$modifier" else ""}", - ) - } - return when (attack.type) { - Attack.Type.PHYSICAL_MELEE_ATTACK -> WeaponUio( - id = attack, - icon = R.drawable.ic_crossed_swords_24, - name = attack.title, - type = "Attaque de corps à corps", - range = "3 mêtres", - hit = hit, - damage = damage, - ) - - Attack.Type.PHYSICAL_RANGE_ATTACK -> WeaponUio( - id = attack, - icon = R.drawable.ic_pocket_bow_24, - name = attack.title, - type = "Attaque à distance", - range = "30 mêtres", - hit = hit, - damage = damage, - ) - - else -> null - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/displayable/ConvertCounterIntoDisplayableFactory.kt b/app/src/main/java/com/pixelized/rplexicon/facotry/displayable/ConvertCounterIntoDisplayableFactory.kt deleted file mode 100644 index db1aafd..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/displayable/ConvertCounterIntoDisplayableFactory.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.pixelized.rplexicon.facotry.displayable - -import com.pixelized.rplexicon.model.Counter -import com.pixelized.rplexicon.ui.screens.character.composable.CounterUio -import javax.inject.Inject - -class ConvertCounterIntoDisplayableFactory @Inject constructor() { - fun toUio(counter: Counter): CounterUio = CounterUio( - title = counter.title ?: "", - value = counter.value, - max = counter.max, - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/model/AssignedSpell.kt b/app/src/main/java/com/pixelized/rplexicon/model/AssignedSpell.kt new file mode 100644 index 0000000..7848a86 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/model/AssignedSpell.kt @@ -0,0 +1,8 @@ +package com.pixelized.rplexicon.model + +data class AssignedSpell( + val hit: Throw?, + val effect: Throw?, + val level: Throw?, + val spell: Spell, +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/model/Attack.kt b/app/src/main/java/com/pixelized/rplexicon/model/Attack.kt index 52c5fc4..79a97e4 100644 --- a/app/src/main/java/com/pixelized/rplexicon/model/Attack.kt +++ b/app/src/main/java/com/pixelized/rplexicon/model/Attack.kt @@ -2,7 +2,7 @@ package com.pixelized.rplexicon.model data class Attack( val title: String, - val type: Type?, + val type: Type, val range: String?, val hit: Throw?, val damage: Throw?, @@ -11,10 +11,4 @@ data class Attack( PHYSICAL_MELEE_ATTACK("Mêlée"), PHYSICAL_RANGE_ATTACK("Distance"), } - - class Throw( - val amount: Int, - val faces: Int, - val modifier: List, - ) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/model/CharacterSheet.kt b/app/src/main/java/com/pixelized/rplexicon/model/CharacterSheet.kt index f058edc..84fa41c 100644 --- a/app/src/main/java/com/pixelized/rplexicon/model/CharacterSheet.kt +++ b/app/src/main/java/com/pixelized/rplexicon/model/CharacterSheet.kt @@ -49,18 +49,10 @@ data class CharacterSheet( val sleightOfHand: Int, // DEX, Représentation val stealth: Int, // DEX, Survie val survival: Int, // WIS, Tromperie -) - - - - - - - - - - - - - +) { + val isWarlock: Boolean get() = characterClass.contains(CLASS_WARLOCK) + companion object { + const val CLASS_WARLOCK = "Occultiste" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/model/Spell.kt b/app/src/main/java/com/pixelized/rplexicon/model/Spell.kt new file mode 100644 index 0000000..aee0f47 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/model/Spell.kt @@ -0,0 +1,24 @@ +package com.pixelized.rplexicon.model + +data class Spell( + val name: String, + val originalName: String, + val school: School, + val level: Int, + val castingTime: String, + val range: String, + val requirement: String, + val duration: String, + val description: String, +) { + enum class School(val key: String) { + ABJURATION("Abjuration"), + DIVINATION("Divination"), + ENCHANTMENT("Enchantement"), + EVOCATION("Évocation"), + ILLUSION("Illusion"), + CONJURATION("Invocation"), + NECROMANCY("Nécromancie"), + TRANSMUTATION("Transmutation"), + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/model/Throw.kt b/app/src/main/java/com/pixelized/rplexicon/model/Throw.kt new file mode 100644 index 0000000..561bd64 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/model/Throw.kt @@ -0,0 +1,7 @@ +package com.pixelized.rplexicon.model + +class Throw( + val amount: Int, + val faces: Int, + val modifier: List, +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/data/ActionRepository.kt b/app/src/main/java/com/pixelized/rplexicon/repository/data/ActionRepository.kt index fedd852..6aeb975 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/data/ActionRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/data/ActionRepository.kt @@ -1,6 +1,6 @@ package com.pixelized.rplexicon.repository.data -import com.pixelized.rplexicon.facotry.model.AttackParser +import com.pixelized.rplexicon.repository.parser.AttackParser import com.pixelized.rplexicon.model.Attack import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/data/AlterationRepository.kt b/app/src/main/java/com/pixelized/rplexicon/repository/data/AlterationRepository.kt index 5a92177..af36f18 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/data/AlterationRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/data/AlterationRepository.kt @@ -1,8 +1,8 @@ package com.pixelized.rplexicon.repository.data -import com.pixelized.rplexicon.facotry.model.alteration.AlterationParser -import com.pixelized.rplexicon.facotry.model.alteration.CounterParser -import com.pixelized.rplexicon.facotry.model.alteration.StatusParser +import com.pixelized.rplexicon.repository.parser.alteration.AlterationParser +import com.pixelized.rplexicon.repository.parser.alteration.CounterParser +import com.pixelized.rplexicon.repository.parser.alteration.StatusParser import com.pixelized.rplexicon.model.Alteration import com.pixelized.rplexicon.model.Counter import com.pixelized.rplexicon.model.Property @@ -21,18 +21,18 @@ class AlterationRepository @Inject constructor( private val statusParser: StatusParser, private val counterParser: CounterParser, ) { - private val _alterations = MutableStateFlow>(emptyList()) - val alterations: StateFlow> get() = _alterations + private val _alterationsLexicon = MutableStateFlow>(emptyList()) + val alterationsLexicon: StateFlow> get() = _alterationsLexicon - private val _status = MutableStateFlow>>(emptyMap()) - val status: StateFlow>> get() = _status + private val _assignedAlterations = MutableStateFlow>>(emptyMap()) + val assignedAlterations: StateFlow>> get() = _assignedAlterations private val _counter = MutableStateFlow>>(emptyMap()) val counter: StateFlow>> get() = _counter fun getAlterations(character: String): List { - return status.value[character]?.mapNotNull { alterationName -> - alterations.value.firstOrNull { it.name == alterationName } + return assignedAlterations.value[character]?.mapNotNull { alterationName -> + alterationsLexicon.value.firstOrNull { it.name == alterationName } } ?: emptyList() } @@ -49,7 +49,7 @@ class AlterationRepository @Inject constructor( googleRepository.fetch { sheet -> val request = sheet.get(Sheet.Character.ID, Sheet.Character.ALTERATION) val data = alterationParser.parse(value = request.execute()) - _alterations.emit(data) + _alterationsLexicon.emit(data) } } @@ -58,7 +58,7 @@ class AlterationRepository @Inject constructor( googleRepository.fetch { sheet -> val request = sheet.get(Sheet.Character.ID, Sheet.Character.STATUS) val status = statusParser.parse(value = request.execute()) - _status.emit(status) + _assignedAlterations.emit(status) val counter = counterParser.parse(values = request.execute()) _counter.emit(counter) } diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/data/CharacterSheetRepository.kt b/app/src/main/java/com/pixelized/rplexicon/repository/data/CharacterSheetRepository.kt index 9520edf..09cadb2 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/data/CharacterSheetRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/data/CharacterSheetRepository.kt @@ -1,6 +1,6 @@ package com.pixelized.rplexicon.repository.data -import com.pixelized.rplexicon.facotry.model.CharacterSheetParser +import com.pixelized.rplexicon.repository.parser.CharacterSheetParser import com.pixelized.rplexicon.model.CharacterSheet import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/data/LexiconRepository.kt b/app/src/main/java/com/pixelized/rplexicon/repository/data/LexiconRepository.kt index a3b309d..83a7ee6 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/data/LexiconRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/data/LexiconRepository.kt @@ -1,6 +1,6 @@ package com.pixelized.rplexicon.repository.data -import com.pixelized.rplexicon.facotry.model.LexiconParser +import com.pixelized.rplexicon.repository.parser.LexiconParser import com.pixelized.rplexicon.model.Lexicon import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/data/LocationRepository.kt b/app/src/main/java/com/pixelized/rplexicon/repository/data/LocationRepository.kt index d7f644b..3a0dcd7 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/data/LocationRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/data/LocationRepository.kt @@ -1,8 +1,8 @@ package com.pixelized.rplexicon.repository.data import com.google.api.services.sheets.v4.model.ValueRange -import com.pixelized.rplexicon.facotry.model.LocationParser -import com.pixelized.rplexicon.facotry.model.MarqueeParser +import com.pixelized.rplexicon.repository.parser.LocationParser +import com.pixelized.rplexicon.repository.parser.MarqueeParser import com.pixelized.rplexicon.model.Location import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/data/QuestRepository.kt b/app/src/main/java/com/pixelized/rplexicon/repository/data/QuestRepository.kt index 01addc3..79785bd 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/data/QuestRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/data/QuestRepository.kt @@ -1,7 +1,7 @@ package com.pixelized.rplexicon.repository.data import com.google.api.services.sheets.v4.model.ValueRange -import com.pixelized.rplexicon.facotry.model.QuestParser +import com.pixelized.rplexicon.repository.parser.QuestParser import com.pixelized.rplexicon.model.Quest import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/data/SpellRepository.kt b/app/src/main/java/com/pixelized/rplexicon/repository/data/SpellRepository.kt new file mode 100644 index 0000000..493aaae --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/repository/data/SpellRepository.kt @@ -0,0 +1,48 @@ +package com.pixelized.rplexicon.repository.data + +import com.pixelized.rplexicon.repository.parser.spell.AssignedSpellParser +import com.pixelized.rplexicon.repository.parser.spell.SpellBookParser +import com.pixelized.rplexicon.model.AssignedSpell +import com.pixelized.rplexicon.model.Spell +import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository +import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure +import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SpellRepository @Inject constructor( + private val googleRepository: GoogleSheetServiceRepository, + private val spellBookParser: SpellBookParser, + private val assignedSpellParser: AssignedSpellParser, +) { + private var spellsBook: List? = null + private val _spells = MutableStateFlow>>(emptyMap()) + val spells: StateFlow>> get() = _spells + + fun findAssignedSpells(character: String?): List? { + return character?.let { _spells.value[it] } + } + + fun findAssignedSpell(character: String?, spell: String): AssignedSpell? { + return character?.let { _spells.value[it]?.find { assigned -> assigned.spell.name == spell } } + } + + @Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class) + suspend fun fetchSpells() { + googleRepository.fetch { sheet -> + val (lexicon, magic) = awaitAll( + async { sheet.get(Sheet.Character.ID, Sheet.Character.MAGIC_LEXICON).execute() }, + async { sheet.get(Sheet.Character.ID, Sheet.Character.MAGIC).execute() }, + ) + val spellsBook = spellBookParser.parse(data = lexicon) + val assignedSpells = assignedSpellParser.parse(data = magic, spells = spellsBook) + this@SpellRepository.spellsBook = spellsBook + _spells.emit(assignedSpells) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/AttackParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/AttackParser.kt similarity index 69% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/AttackParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/AttackParser.kt index 92278eb..9c5b3ff 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/AttackParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/AttackParser.kt @@ -1,19 +1,16 @@ -package com.pixelized.rplexicon.facotry.model +package com.pixelized.rplexicon.repository.parser import android.util.Log import com.google.api.services.sheets.v4.model.ValueRange -import com.pixelized.rplexicon.facotry.model.roll.DiceParser -import com.pixelized.rplexicon.facotry.model.roll.ModifierParser import com.pixelized.rplexicon.model.Attack import com.pixelized.rplexicon.model.CharacterSheet import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure -import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure +import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure import com.pixelized.rplexicon.utilitary.extentions.sheet import javax.inject.Inject class AttackParser @Inject constructor( - private val diceParser: DiceParser, - private val modifierParser: ModifierParser, + private val throwParser: ThrowParser, ) { @Throws(IncompatibleSheetStructure::class) fun parse( @@ -45,8 +42,8 @@ class AttackParser @Inject constructor( title = title, type = parseType(value = row.parseString(TYPE)), range = row.parseString(RANGE), - hit = parseThrows(value = row.parseString(HIT)), - damage = parseThrows(value = row.parseString(DAMAGE)), + hit = throwParser.parse(value = row.parseString(HIT)), + damage = throwParser.parse(value = row.parseString(DAMAGE)), ) actions .getOrPut(characterSheet.name) { mutableListOf() } @@ -59,27 +56,12 @@ class AttackParser @Inject constructor( return actions } - private fun parseType(value: String?): Attack.Type? = try { + private fun parseType(value: String?): Attack.Type = try { Attack.Type.values().firstOrNull { it.key == value } } catch (exception: Exception) { Log.e("ActionParser", exception.message, exception) null - } - - private fun parseThrows(value: String?): Attack.Throw? { - if (value != null) { - val dice = diceParser.findAll(value = value).firstOrNull() - if (dice != null) { - val modifier = modifierParser.findAll(value = value) - return Attack.Throw( - amount = dice.count, - faces = dice.faces, - modifier = modifier, - ) - } - } - return null - } + } ?: Attack.Type.PHYSICAL_MELEE_ATTACK companion object { const val NAME = "Nom" diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/CharacterSheetParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/CharacterSheetParser.kt similarity index 98% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/CharacterSheetParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/CharacterSheetParser.kt index 8beb6d9..29681c5 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/CharacterSheetParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/CharacterSheetParser.kt @@ -1,10 +1,10 @@ -package com.pixelized.rplexicon.facotry.model +package com.pixelized.rplexicon.repository.parser import com.google.api.services.sheets.v4.model.ValueRange -import com.pixelized.rplexicon.facotry.model.alteration.CounterParser +import com.pixelized.rplexicon.repository.parser.alteration.CounterParser import com.pixelized.rplexicon.model.CharacterSheet import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure -import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure +import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure import com.pixelized.rplexicon.utilitary.extentions.sheet import javax.inject.Inject diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/GenderParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/GenderParser.kt similarity index 90% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/GenderParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/GenderParser.kt index b3d804d..425c5eb 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/GenderParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/GenderParser.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.facotry.model +package com.pixelized.rplexicon.repository.parser import com.pixelized.rplexicon.model.Lexicon import javax.inject.Inject diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/LexiconParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/LexiconParser.kt similarity index 96% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/LexiconParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/LexiconParser.kt index 1f2577f..e7c3dbd 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/LexiconParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/LexiconParser.kt @@ -1,9 +1,9 @@ -package com.pixelized.rplexicon.facotry.model +package com.pixelized.rplexicon.repository.parser import com.google.api.services.sheets.v4.model.ValueRange import com.pixelized.rplexicon.model.Lexicon import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure -import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure +import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure import com.pixelized.rplexicon.utilitary.extentions.sheet import javax.inject.Inject diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/LocationParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/LocationParser.kt similarity index 93% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/LocationParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/LocationParser.kt index 9b81042..2536c1b 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/LocationParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/LocationParser.kt @@ -1,10 +1,10 @@ -package com.pixelized.rplexicon.facotry.model +package com.pixelized.rplexicon.repository.parser import android.net.Uri import com.google.api.services.sheets.v4.model.ValueRange import com.pixelized.rplexicon.model.Location import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure -import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure +import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure import com.pixelized.rplexicon.utilitary.extentions.sheet import javax.inject.Inject diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/MarqueeParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/MarqueeParser.kt similarity index 95% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/MarqueeParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/MarqueeParser.kt index 1817ffd..5ca6290 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/MarqueeParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/MarqueeParser.kt @@ -1,9 +1,9 @@ -package com.pixelized.rplexicon.facotry.model +package com.pixelized.rplexicon.repository.parser import androidx.compose.ui.geometry.Offset import com.google.api.services.sheets.v4.model.ValueRange import com.pixelized.rplexicon.model.Location -import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure +import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure import com.pixelized.rplexicon.utilitary.extentions.sheet import javax.inject.Inject diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/PortraitParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/PortraitParser.kt similarity index 88% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/PortraitParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/PortraitParser.kt index f7fe67d..36b1844 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/PortraitParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/PortraitParser.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.facotry.model +package com.pixelized.rplexicon.repository.parser import android.net.Uri import com.pixelized.rplexicon.utilitary.extentions.toUriOrNull diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/QuestParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/QuestParser.kt similarity index 96% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/QuestParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/QuestParser.kt index c8fd2fc..d2cf969 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/QuestParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/QuestParser.kt @@ -1,9 +1,9 @@ -package com.pixelized.rplexicon.facotry.model +package com.pixelized.rplexicon.repository.parser import com.google.api.services.sheets.v4.model.ValueRange import com.pixelized.rplexicon.model.QuestEntry -import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure +import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure import com.pixelized.rplexicon.utilitary.extentions.sheet import com.pixelized.rplexicon.utilitary.extentions.toUriOrNull import javax.inject.Inject diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/RaceParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/RaceParser.kt similarity index 96% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/RaceParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/RaceParser.kt index 9d45fc6..587f22f 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/RaceParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/RaceParser.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.facotry.model +package com.pixelized.rplexicon.repository.parser import com.pixelized.rplexicon.model.Lexicon import javax.inject.Inject diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/parser/ThrowParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/ThrowParser.kt new file mode 100644 index 0000000..2476d85 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/ThrowParser.kt @@ -0,0 +1,26 @@ +package com.pixelized.rplexicon.repository.parser + +import com.pixelized.rplexicon.repository.parser.roll.DiceParser +import com.pixelized.rplexicon.repository.parser.roll.ModifierParser +import com.pixelized.rplexicon.model.Throw +import javax.inject.Inject + +class ThrowParser @Inject constructor( + private val diceParser: DiceParser, + private val modifierParser: ModifierParser, +) { + fun parse(value: String?): Throw? { + if (value != null) { + val dice = diceParser.findAll(value = value).firstOrNull() + if (dice != null) { + val modifier = modifierParser.findAll(value = value) + return Throw( + amount = dice.count, + faces = dice.faces, + modifier = modifier, + ) + } + } + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/alteration/AlterationParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/AlterationParser.kt similarity index 92% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/alteration/AlterationParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/AlterationParser.kt index f0d2221..37ddfe7 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/alteration/AlterationParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/AlterationParser.kt @@ -1,12 +1,12 @@ -package com.pixelized.rplexicon.facotry.model.alteration +package com.pixelized.rplexicon.repository.parser.alteration import com.google.api.services.sheets.v4.model.ValueRange -import com.pixelized.rplexicon.facotry.model.roll.DiceParser -import com.pixelized.rplexicon.facotry.model.roll.FlatValueParser +import com.pixelized.rplexicon.repository.parser.roll.DiceParser +import com.pixelized.rplexicon.repository.parser.roll.FlatValueParser import com.pixelized.rplexicon.model.Alteration import com.pixelized.rplexicon.model.Property import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure -import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure +import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure import com.pixelized.rplexicon.utilitary.extentions.sheet import javax.inject.Inject diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/alteration/CounterParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/CounterParser.kt similarity index 97% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/alteration/CounterParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/CounterParser.kt index a7a0e2a..6ba6933 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/alteration/CounterParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/CounterParser.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.facotry.model.alteration +package com.pixelized.rplexicon.repository.parser.alteration import com.google.api.services.sheets.v4.model.ValueRange import com.pixelized.rplexicon.model.Counter diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/alteration/PropertyParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/PropertyParser.kt similarity index 86% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/alteration/PropertyParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/PropertyParser.kt index ff987a0..7500796 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/alteration/PropertyParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/PropertyParser.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.facotry.model.alteration +package com.pixelized.rplexicon.repository.parser.alteration import android.util.Log import com.pixelized.rplexicon.model.Property diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/alteration/StatusParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/StatusParser.kt similarity index 96% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/alteration/StatusParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/StatusParser.kt index 6411de0..838bad5 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/alteration/StatusParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/alteration/StatusParser.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.facotry.model.alteration +package com.pixelized.rplexicon.repository.parser.alteration import com.google.api.services.sheets.v4.model.ValueRange import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/roll/DiceParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/roll/DiceParser.kt similarity index 93% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/roll/DiceParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/roll/DiceParser.kt index 9fe1efd..a8fe9ee 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/roll/DiceParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/roll/DiceParser.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.facotry.model.roll +package com.pixelized.rplexicon.repository.parser.roll import com.pixelized.rplexicon.model.Roll import javax.inject.Inject diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/roll/FlatValueParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/roll/FlatValueParser.kt similarity index 91% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/roll/FlatValueParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/roll/FlatValueParser.kt index 5a9d1e1..05a63cf 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/roll/FlatValueParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/roll/FlatValueParser.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.facotry.model.roll +package com.pixelized.rplexicon.repository.parser.roll import com.pixelized.rplexicon.model.Roll import javax.inject.Inject diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/model/roll/ModifierParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/roll/ModifierParser.kt similarity index 86% rename from app/src/main/java/com/pixelized/rplexicon/facotry/model/roll/ModifierParser.kt rename to app/src/main/java/com/pixelized/rplexicon/repository/parser/roll/ModifierParser.kt index 927ac37..3dc8390 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/model/roll/ModifierParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/roll/ModifierParser.kt @@ -1,6 +1,6 @@ -package com.pixelized.rplexicon.facotry.model.roll +package com.pixelized.rplexicon.repository.parser.roll -import com.pixelized.rplexicon.facotry.model.alteration.PropertyParser +import com.pixelized.rplexicon.repository.parser.alteration.PropertyParser import com.pixelized.rplexicon.model.Property import javax.inject.Inject diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/parser/spell/AssignedSpellParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/spell/AssignedSpellParser.kt new file mode 100644 index 0000000..25ffdbe --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/spell/AssignedSpellParser.kt @@ -0,0 +1,67 @@ +package com.pixelized.rplexicon.repository.parser.spell + +import com.google.api.services.sheets.v4.model.ValueRange +import com.pixelized.rplexicon.repository.parser.ThrowParser +import com.pixelized.rplexicon.model.AssignedSpell +import com.pixelized.rplexicon.model.Spell +import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure +import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure +import com.pixelized.rplexicon.utilitary.extentions.sheet +import javax.inject.Inject + +class AssignedSpellParser @Inject constructor( + private val throwParser: ThrowParser, +) { + @Throws(IncompatibleSheetStructure::class) + fun parse(data: ValueRange, spells: List): Map> { + val sheet = data.values.sheet() + val spellsBook = spells.associateBy { it.name } + lateinit var structure: Map + val assignedSpells = hashMapOf>() + + // declare helper method to parse String + fun List<*>.parse(key: String): String? = + (getOrNull(structure.getValue(key)) as? String)?.takeIf { it.isNotEmpty() } + + sheet?.forEachIndexed { index, row -> + when { + index == 0 -> { + structure = row.checkSheetStructure(model = COLUMNS) + } + + row is List<*> -> { + val character = row.getOrNull(0) as? String + val spell = spellsBook[row.parse(NAME)] + if (character != null && spell != null) { + val assignedSpell = AssignedSpell( + hit = throwParser.parse(value = row.parse(HIT)), + effect = throwParser.parse(value = row.parse(EFFECT)), + level = throwParser.parse(value = row.parse(LEVEL)), + spell = spell, + ) + assignedSpells + .getOrPut(key = character) { mutableListOf() } + .add(assignedSpell) + } + } + } + } + + return assignedSpells + } + + companion object { + private const val NAME = "Nom" + private const val HIT = "Touché" + private const val EFFECT = "Effet" + private const val LEVEL = "Par niveau" + + private val COLUMNS + get() = listOf( + NAME, + HIT, + EFFECT, + LEVEL, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/parser/spell/SpellBookParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/spell/SpellBookParser.kt new file mode 100644 index 0000000..be47265 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/spell/SpellBookParser.kt @@ -0,0 +1,103 @@ +package com.pixelized.rplexicon.repository.parser.spell + +import android.util.Log +import com.google.api.services.sheets.v4.model.ValueRange +import com.pixelized.rplexicon.model.Spell +import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure +import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure +import com.pixelized.rplexicon.utilitary.extentions.sheet +import javax.inject.Inject + +class SpellBookParser @Inject constructor() { + + @Throws(IncompatibleSheetStructure::class) + fun parse(data: ValueRange): List { + + val sheet = data.values.sheet() + + lateinit var structure: Map + + // declare helper method to parse String + fun List<*>.parse(key: String): String? = + (getOrNull(structure.getValue(key)) as? String)?.takeIf { it.isNotEmpty() } + + return sheet?.mapIndexedNotNull { index, row -> + when { + index == 0 -> { + structure = row.checkSheetStructure(model = COLUMNS) + null + } + + row is List<*> -> { + val name = row.parse(NAME) + val level = row.parse(LEVEL)?.toIntOrNull() + val originalName = row.parse(T_NAME) + val school = parseSchool(row.parse(SCHOOL)) + val castingTime = row.parse(CASTING_TIME) + val range = row.parse(RANGE) + val requirement = row.parse(REQUIREMENT) + val duration = row.parse(DURATION) + val description = row.parse(DESCRIPTION) + if (name != null + && level != null + && originalName != null + && school != null + && castingTime != null + && range != null + && requirement != null + && duration != null + && description != null + ) { + Spell( + name = name, + originalName = originalName, + level = level, + school = school, + castingTime = castingTime, + range = range, + requirement = requirement, + duration = duration, + description = description, + ) + } else { + null + } + } + + else -> null + } + } ?: emptyList() + } + + private fun parseSchool(value: String?): Spell.School? = try { + Spell.School.values().firstOrNull { it.key == value } + } catch (exception: Exception) { + Log.e("ActionParser", exception.message, exception) + null + } + + companion object { + private const val NAME = "Nom" + private const val T_NAME = "Traduction" + private const val SCHOOL = "École" + private const val LEVEL = "Niveau" + private const val CASTING_TIME = "Temps d'incantation" + private const val RANGE = "Portée" + private const val REQUIREMENT = "Composantes" + private const val DURATION = "Durée" + private const val DESCRIPTION = "Description" + + private val COLUMNS + get() = listOf( + NAME, + T_NAME, + LEVEL, + SCHOOL, + CASTING_TIME, + RANGE, + REQUIREMENT, + DURATION, + DESCRIPTION, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/composable/BackgroundImage.kt b/app/src/main/java/com/pixelized/rplexicon/ui/composable/BackgroundImage.kt index bb855c9..40ad20c 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/composable/BackgroundImage.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/composable/BackgroundImage.kt @@ -2,18 +2,46 @@ package com.pixelized.rplexicon.ui.composable import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorMatrix +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.ContentScale import com.pixelized.rplexicon.R import com.skydoves.landscapist.ImageOptions + +@Composable +fun BackgroundImage( + modifier: Modifier = Modifier, + tint: Color = LocalContentColor.current, + painter: Painter, +) { + Box( + modifier = modifier + ) { + Icon( + modifier = Modifier.matchParentSize(), + tint = tint, + painter = painter, + contentDescription = null, + ) + Box( + modifier = Modifier + .matchParentSize() + .background(brush = rememberBackgroundGradient()) + ) + } +} + @Composable fun BackgroundImage( modifier: Modifier = Modifier, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/HomeNavHost.kt b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/HomeNavHost.kt index 6a82fef..a8a9331 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/HomeNavHost.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/HomeNavHost.kt @@ -132,19 +132,19 @@ private fun rememberBottomBarItems( listOf( BottomBarItem( route = LEXICON_LIST_ROUTE, - icon = R.drawable.ic_outline_account_circle_24, + icon = R.drawable.ic_visored_helm_24, label = R.string.home_lexicon, onClick = { navHostController.navigateToLexicon(option) } ), BottomBarItem( route = QUEST_LIST_ROUTE, - icon = R.drawable.ic_outline_scroll_24, + icon = R.drawable.ic_scroll_unfurled_24, label = R.string.home_quest_log, onClick = { navHostController.navigateToQuestList(option) } ), BottomBarItem( route = LOCATION_LIST_ROUTE, - icon = R.drawable.ic_outline_map_24, + icon = R.drawable.ic_treasure_map_24, label = R.string.home_location, onClick = { navHostController.navigateToLocation(option) } ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/ScreenNavHost.kt b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/ScreenNavHost.kt index 0fa4d2d..0f917c2 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/ScreenNavHost.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/ScreenNavHost.kt @@ -17,6 +17,7 @@ import com.pixelized.rplexicon.ui.navigation.screens.composableLexiconDetail import com.pixelized.rplexicon.ui.navigation.screens.composableLexiconSearch import com.pixelized.rplexicon.ui.navigation.screens.composableLocationDetail import com.pixelized.rplexicon.ui.navigation.screens.composableQuestDetail +import com.pixelized.rplexicon.ui.navigation.screens.composableSpellDetail val LocalScreenNavHost = staticCompositionLocalOf { error("LocalScreenNavHost not ready") @@ -49,6 +50,7 @@ fun ScreenNavHost( composableQuestDetail() composableLocationDetail() composableCharacterSheet() + composableSpellDetail() } } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableSpellDetail.kt b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableSpellDetail.kt new file mode 100644 index 0000000..1f088ff --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableSpellDetail.kt @@ -0,0 +1,61 @@ +package com.pixelized.rplexicon.ui.navigation.screens + +import androidx.compose.runtime.Stable +import androidx.lifecycle.SavedStateHandle +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.NavOptionsBuilder +import androidx.navigation.NavType +import androidx.navigation.navArgument +import com.pixelized.rplexicon.ui.navigation.NavigationAnimation +import com.pixelized.rplexicon.ui.navigation.animatedComposable +import com.pixelized.rplexicon.ui.screens.spell.SpellDetailScreen +import com.pixelized.rplexicon.utilitary.extentions.ARG + +private const val ROUTE = "spellDetail" +private const val ARG_CHARACTER = "character" +private const val ARG_SPELL = "spell" + +val SPELL_DETAIL_ROUTE = "$ROUTE?${ARG_CHARACTER.ARG}&${ARG_SPELL.ARG}" + +@Stable +data class SpellDetailArgument( + val character: String, + val spell: String, +) + +val SavedStateHandle.spellDetailArgument: SpellDetailArgument + get() = SpellDetailArgument( + character = get(ARG_CHARACTER) ?: error("Missing $ARG_CHARACTER argument from $this"), + spell = get(ARG_SPELL) ?: error("Missing $ARG_SPELL argument from $this"), + ) + +fun NavGraphBuilder.composableSpellDetail() { + animatedComposable( + route = SPELL_DETAIL_ROUTE, + arguments = listOf( + navArgument(name = ARG_CHARACTER) { + type = NavType.StringType + nullable = false + }, + navArgument(name = ARG_SPELL) { + type = NavType.StringType + nullable = false + } + ), + animation = NavigationAnimation.Push, + ) { + SpellDetailScreen() + } +} + +fun NavHostController.navigateToSpellDetail( + character: String, + spell: String, + option: NavOptionsBuilder.() -> Unit = {}, +) { + val route = ROUTE + + "?${ARG_CHARACTER}=$character" + + "&${ARG_SPELL}=$spell" + navigate(route = route, builder = option) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt index 9e4f72c..46c27b9 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetScreen.kt @@ -1,26 +1,33 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + package com.pixelized.rplexicon.ui.screens.character import android.content.res.Configuration import androidx.activity.compose.BackHandler import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerScope import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetLayout +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.pullrefresh.PullRefreshState import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -29,139 +36,123 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.LocalRollOverlay -import com.pixelized.rplexicon.NO_WINDOW_INSETS import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.composable.Handle import com.pixelized.rplexicon.ui.composable.Loader import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost -import com.pixelized.rplexicon.ui.screens.character.composable.LabelPoint -import com.pixelized.rplexicon.ui.screens.character.composable.LabelPointUio -import com.pixelized.rplexicon.ui.screens.character.composable.ProficiencyUio -import com.pixelized.rplexicon.ui.screens.character.composable.StatUio -import com.pixelized.rplexicon.ui.screens.character.composable.WeaponUio -import com.pixelized.rplexicon.ui.screens.character.pages.ActionsPages -import com.pixelized.rplexicon.ui.screens.character.pages.ProficiencyPage -import com.pixelized.rplexicon.ui.screens.character.preview.rememberCharacterSheetPreview -import com.pixelized.rplexicon.ui.screens.character.preview.rememberWeaponListStatePreview +import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeader +import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderPreview +import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio.ID.* +import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio.ID.* +import com.pixelized.rplexicon.ui.screens.character.pages.actions.ActionPage +import com.pixelized.rplexicon.ui.screens.character.pages.actions.ActionPagePreview +import com.pixelized.rplexicon.ui.screens.character.pages.actions.AttackActionViewModel +import com.pixelized.rplexicon.ui.screens.character.pages.actions.SpellsActionViewModel +import com.pixelized.rplexicon.ui.screens.character.pages.chooser.SpellLevelChooser +import com.pixelized.rplexicon.ui.screens.character.pages.chooser.SpellLevelChooserPreview +import com.pixelized.rplexicon.ui.screens.character.pages.proficiency.ProficiencyPage +import com.pixelized.rplexicon.ui.screens.character.pages.proficiency.ProficiencyPreview +import com.pixelized.rplexicon.ui.screens.character.pages.proficiency.ProficiencyViewModel import com.pixelized.rplexicon.ui.theme.LexiconTheme import kotlinx.coroutines.launch -@Stable -data class CharacterSheetUio( - val armorClass: LabelPointUio, - val speed: LabelPointUio, - val hitPoint: LabelPointUio, - val initiative: LabelPointUio, - val stats: List, - val savingThrows: List, - val proficiencies: List, +@OptIn( + ExperimentalMaterialApi::class, + ExperimentalFoundationApi::class, + ExperimentalMaterial3Api::class, ) - -@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) @Composable fun CharacterSheetScreen( viewModel: CharacterSheetViewModel = hiltViewModel(), + proficiencyViewModel: ProficiencyViewModel = hiltViewModel(), + attackViewModel: AttackActionViewModel = hiltViewModel(), + spellsViewModel: SpellsActionViewModel = hiltViewModel(), ) { val screen = LocalScreenNavHost.current val overlay = LocalRollOverlay.current val scope = rememberCoroutineScope() - + val sheetState = rememberModalBottomSheetState( + initialValue = ModalBottomSheetValue.Hidden, + skipHalfExpanded = true, + ) val refresh = rememberPullRefreshState( refreshing = false, onRefresh = { scope.launch { viewModel.update() } }, ) - Surface( modifier = Modifier.fillMaxSize(), ) { - viewModel.sheet.value?.let { - CharacterSheetContent( - modifier = Modifier - .fillMaxSize() - .systemBarsPadding(), - pagerState = rememberPagerState { - 2 // TODO - }, - refreshing = viewModel.isLoading, - refreshState = refresh, - onRefresh = { scope.launch { viewModel.update() } }, - sheet = it, - weapons = viewModel.weapons, - onBack = { - screen.popBackStack() - }, - onInitiative = { - val roll = viewModel.initiativeRoll() - overlay.prepareRoll(roll = roll) - overlay.showOverlay() - }, - onStats = { stat -> - val roll = when (stat.id) { - StatUio.ID.STRENGTH -> viewModel.strengthRoll() - StatUio.ID.DEXTERITY -> viewModel.dexterityRoll() - StatUio.ID.CONSTITUTION -> viewModel.constitutionRoll() - StatUio.ID.INTELLIGENCE -> viewModel.intelligenceRoll() - StatUio.ID.WISDOM -> viewModel.wisdomRoll() - StatUio.ID.CHARISMA -> viewModel.charismaRoll() - } - overlay.prepareRoll(roll = roll) - overlay.showOverlay() - }, - onProficiencies = { proficiency -> - val roll = when (proficiency.id) { - ProficiencyUio.ID.STRENGTH_SAVING_THROW -> viewModel.strengthRoll() - ProficiencyUio.ID.DEXTERITY_SAVING_THROW -> viewModel.dexterityRoll() - ProficiencyUio.ID.CONSTITUTION_SAVING_THROW -> viewModel.constitutionRoll() - ProficiencyUio.ID.INTELLIGENCE_SAVING_THROW -> viewModel.intelligenceRoll() - ProficiencyUio.ID.WISDOM_SAVING_THROW -> viewModel.wisdomRoll() - ProficiencyUio.ID.CHARISMA_SAVING_THROW -> viewModel.charismaRoll() - ProficiencyUio.ID.ACROBATICS -> viewModel.acrobaticsRoll() - ProficiencyUio.ID.ANIMAL_HANDLING -> viewModel.animalHandlingRoll() - ProficiencyUio.ID.ARCANA -> viewModel.arcanaRoll() - ProficiencyUio.ID.ATHLETICS -> viewModel.athleticsRoll() - ProficiencyUio.ID.DECEPTION -> viewModel.deceptionRoll() - ProficiencyUio.ID.HISTORY -> viewModel.historyRoll() - ProficiencyUio.ID.INSIGHT -> viewModel.insightRoll() - ProficiencyUio.ID.INTIMIDATION -> viewModel.intimidationRoll() - ProficiencyUio.ID.INVESTIGATION -> viewModel.investigationRoll() - ProficiencyUio.ID.MEDICINE -> viewModel.medicineRoll() - ProficiencyUio.ID.NATURE -> viewModel.natureRoll() - ProficiencyUio.ID.PERCEPTION -> viewModel.perceptionRoll() - ProficiencyUio.ID.PERFORMANCE -> viewModel.performanceRoll() - ProficiencyUio.ID.PERSUASION -> viewModel.persuasionRoll() - ProficiencyUio.ID.RELIGION -> viewModel.religionRoll() - ProficiencyUio.ID.SLEIGHT_OF_HAND -> viewModel.sleightOfHandRoll() - ProficiencyUio.ID.STEALTH -> viewModel.stealthRoll() - ProficiencyUio.ID.SURVIVAL -> viewModel.survivalRoll() - } - overlay.prepareRoll(roll = roll) - overlay.showOverlay() - }, - onHit = { id -> - val roll = viewModel.onHitRoll(id) - overlay.prepareRoll(roll = roll) - overlay.showOverlay() - }, - onDamage = { id -> - val roll = viewModel.onDamageRoll(id) - overlay.prepareRoll(roll = roll) - overlay.showOverlay() - } - ) + CharacterSheetContent( + modifier = Modifier.fillMaxSize(), + pagerState = rememberPagerState { + val haveSheet = proficiencyViewModel.sheet.value != null + val haveAction = attackViewModel.attacks.value.isNotEmpty() + val haveSpell = spellsViewModel.spells.value.isNotEmpty() + (if (haveSheet) 1 else 0) + (if (haveAction || haveSpell) 1 else 0) + }, + sheetState = sheetState, + refreshState = refresh, + onRefresh = { + scope.launch { viewModel.update() } + }, + onBack = { + screen.popBackStack() + }, + header = { + CharacterSheetHeader( + modifier = Modifier.zIndex(1f), + header = viewModel.header, + ) + }, + loader = { + Loader( + modifier = Modifier.align(Alignment.TopCenter), + refreshState = refresh, + refreshing = viewModel.isLoading, + ) + }, + proficiencies = { + ProficiencyPage( + viewModel = proficiencyViewModel, + ) + }, + actions = { + ActionPage( + character = viewModel.character, + sheetState = sheetState, + attackViewModel = attackViewModel, + spellsViewModel = spellsViewModel, + ) + }, + sheet = { + SpellLevelChooser( + spells = spellsViewModel.preparedSpellLevel, + onLevel = { spell, level -> + scope.launch { sheetState.hide() } + val roll = spellsViewModel.onCastSpell(spell, level) + overlay.prepareRoll(roll = roll) + overlay.showOverlay() + }, + ) + }, + ) + + BackHandler(enabled = sheetState.isVisible) { + scope.launch { sheetState.hide() } } BackHandler(enabled = overlay.isOverlayVisible) { @@ -179,22 +170,19 @@ fun CharacterSheetScreen( private fun CharacterSheetContent( modifier: Modifier = Modifier, pagerState: PagerState, + sheetState: ModalBottomSheetState, refreshState: PullRefreshState, - refreshing: State, onRefresh: () -> Unit, - sheet: CharacterSheetUio, - weapons: State>, onBack: () -> Unit, - onStats: (StatUio) -> Unit, - onProficiencies: (ProficiencyUio) -> Unit, - onInitiative: () -> Unit, - onHit: (id: String) -> Unit, - onDamage: (id: String) -> Unit, + header: @Composable ColumnScope.() -> Unit, + loader: @Composable BoxScope.() -> Unit, + proficiencies: @Composable PagerScope.() -> Unit, + actions: @Composable PagerScope.() -> Unit, + sheet: @Composable () -> Unit, ) { Scaffold( modifier = modifier, containerColor = Color.Transparent, - contentWindowInsets = NO_WINDOW_INSETS, topBar = { TopAppBar( navigationIcon = { @@ -220,97 +208,82 @@ private fun CharacterSheetContent( }, ) }, - ) { paddingValues -> - Surface( - modifier = Modifier.padding(paddingValues = paddingValues), - ) { - Column { - Surface( - modifier = Modifier.shadow(elevation = 4.dp), - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - verticalAlignment = Alignment.Bottom, - horizontalArrangement = Arrangement.spacedBy( - space = 16.dp, - alignment = Alignment.CenterHorizontally - ), - ) { - LabelPoint(label = sheet.armorClass) - LabelPoint(label = sheet.hitPoint) - LabelPoint(label = sheet.speed) - } - } + content = { paddingValues -> + Surface( + modifier = Modifier.padding(paddingValues = paddingValues), + ) { - Box( - modifier = Modifier.fillMaxWidth(), - ) { - HorizontalPager( - modifier = Modifier - .fillMaxWidth() - .pullRefresh(refreshState), - state = pagerState, - contentPadding = PaddingValues(horizontal = 16.dp), - pageSpacing = 8.dp, - verticalAlignment = Alignment.Top, - ) { - when (it) { - 0 -> ProficiencyPage( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - sheet = sheet, - onInitiative = onInitiative, - onStats = onStats, - onProficiencies = onProficiencies, - ) + ModalBottomSheetLayout( + sheetState = sheetState, + sheetContent = { + Surface { + sheet() + } + }, + content = { + Column { + header() - 1 -> ActionsPages( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - weapons = weapons, - onHit = onHit, - onDamage = onDamage, - ) + Box( + modifier = Modifier.fillMaxWidth(), + ) { + HorizontalPager( + modifier = Modifier + .fillMaxWidth() + .pullRefresh(refreshState), + state = pagerState, + beyondBoundsPageCount = 1, + verticalAlignment = Alignment.Top, + pageContent = { page -> + when (page) { + 0 -> proficiencies() + 1 -> actions() + } + } + ) + + loader() + } } } - - Loader( - modifier = Modifier.align(Alignment.TopCenter), - refreshState = refreshState, - refreshing = refreshing, - ) - } + ) } - } - } + }, + ) } @OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) @Composable @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -private fun CharacterScreenPreview() { +private fun CharacterScreenPreview( + @PreviewParameter(CharacterScreenPreviewProvider::class) preview: Int, +) { LexiconTheme { Surface { + val sheetState = rememberModalBottomSheetState( + initialValue = when (preview == 2) { + true -> ModalBottomSheetValue.Expanded + else -> ModalBottomSheetValue.Hidden + }, + ) CharacterSheetContent( modifier = Modifier.fillMaxSize(), - pagerState = rememberPagerState { 1 }, + sheetState = sheetState, + pagerState = rememberPagerState(initialPage = preview) { 2 }, refreshState = rememberPullRefreshState(refreshing = false, onRefresh = { }), - refreshing = remember { mutableStateOf(false) }, onRefresh = { }, - sheet = rememberCharacterSheetPreview(), - weapons = rememberWeaponListStatePreview(), onBack = { }, - onInitiative = { }, - onStats = { }, - onProficiencies = { }, - onHit = { }, - onDamage = { }, + header = { CharacterSheetHeaderPreview() }, + loader = { }, + proficiencies = { ProficiencyPreview() }, + actions = { ActionPagePreview() }, + sheet = { SpellLevelChooserPreview() }, ) } } +} + +private class CharacterScreenPreviewProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf(0, 1, 2) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetViewModel.kt index 2dc69d2..b986648 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/CharacterSheetViewModel.kt @@ -1,119 +1,49 @@ package com.pixelized.rplexicon.ui.screens.character -import android.app.Application import android.util.Log -import androidx.annotation.StringRes import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf -import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.pixelized.rplexicon.R -import com.pixelized.rplexicon.facotry.displayable.ConvertAttackIntoDisplayableFactory -import com.pixelized.rplexicon.facotry.displayable.ConvertCharacterSheetIntoDisplayableFactory -import com.pixelized.rplexicon.facotry.displayable.ConvertCounterIntoDisplayableFactory -import com.pixelized.rplexicon.model.Attack -import com.pixelized.rplexicon.model.CharacterSheet -import com.pixelized.rplexicon.model.Property -import com.pixelized.rplexicon.model.Roll -import com.pixelized.rplexicon.model.d20 import com.pixelized.rplexicon.repository.data.ActionRepository import com.pixelized.rplexicon.repository.data.AlterationRepository import com.pixelized.rplexicon.repository.data.CharacterSheetRepository +import com.pixelized.rplexicon.repository.data.SpellRepository import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument -import com.pixelized.rplexicon.ui.screens.character.composable.ActionsUio -import com.pixelized.rplexicon.ui.screens.character.composable.CounterUio -import com.pixelized.rplexicon.ui.screens.character.composable.WeaponUio -import com.pixelized.rplexicon.utilitary.extentions.context -import com.pixelized.rplexicon.utilitary.extentions.modifier +import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio +import com.pixelized.rplexicon.ui.screens.character.factory.CharacterSheetHeaderUioFactory import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject @HiltViewModel class CharacterSheetViewModel @Inject constructor( - application: Application, savedStateHandle: SavedStateHandle, - private val characterSheetFactory: ConvertCharacterSheetIntoDisplayableFactory, - private val counterFactory: ConvertCounterIntoDisplayableFactory, - private val weaponFactory: ConvertAttackIntoDisplayableFactory, private val characterRepository: CharacterSheetRepository, private val alterationRepository: AlterationRepository, private val actionRepository: ActionRepository, -) : AndroidViewModel(application = application) { - private val argument = savedStateHandle.characterSheetArgument + private val spellRepository: SpellRepository, + headerFactory: CharacterSheetHeaderUioFactory, +) : ViewModel() { + val character = savedStateHandle.characterSheetArgument.name - private lateinit var model: CharacterSheet - val sheet: State - val alterations: State> - val actions: State> - val weapons: State> - val counter: State> + private val _header = mutableStateOf(null) + val header: State get() = _header private val _isLoading = mutableStateOf(false) val isLoading: State get() = _isLoading init { - sheet = mutableStateOf( - characterRepository.find(name = argument.name)?.let { - model = it - characterSheetFactory.toUio(sheet = model) - } - ) - alterations = mutableStateOf( - alterationRepository.getAlterations(argument.name).map { - it.name - } - ) - counter = mutableStateOf( - alterationRepository.getCounter(argument.name)?.map { - counterFactory.toUio(counter = it) - } ?: emptyList() - ) - actions = mutableStateOf( - actionRepository.find(name = argument.name)?.mapNotNull { -// actionFactory.toUio(attack = it) - null - } ?: emptyList() - ) - weapons = mutableStateOf( - actionRepository.find(name = argument.name)?.mapNotNull { - weaponFactory.toUio(attack = it, characterSheet = model) - } ?: emptyList() - ) - viewModelScope.launch { - launch { - characterRepository.data.collect { - model = it.getValue(key = argument.name) - sheet.value = characterSheetFactory.toUio(sheet = model) - } - } - launch { - actionRepository.data.collect { -// actions.value = it[argument.name]?.mapNotNull { action -> -// actionFactory.toUio(attack = action) -// } ?: emptyList() - weapons.value = it[argument.name]?.mapNotNull { action -> - weaponFactory.toUio(attack = action, characterSheet = model) - } ?: emptyList() - } - } - launch { - alterationRepository.counter.collect { - counter.value = it[argument.name]?.map { counter -> - counterFactory.toUio(counter = counter) - } ?: emptyList() - } - } - launch { - alterationRepository.status.collect { - alterations.value = it[argument.name]?.mapNotNull { name -> - name.takeIf { value -> value.isNotEmpty() } - } ?: emptyList() + characterRepository.data.collect { sheets -> + _header.value = withContext(Dispatchers.Default) { + headerFactory.convert(model = sheets.getValue(character)) } } } @@ -143,514 +73,22 @@ class CharacterSheetViewModel @Inject constructor( Log.e(TAG, exception.message, exception) } } - awaitAll(characterRequest, statusRequest, alterationRequest) - _isLoading.value = false - } - - fun onHitRoll(id: String): Roll { - val action = actionRepository.find(argument.name)?.firstOrNull { it.title == id } - return actionRoll( - attack = action, - throws = action?.hit, - ) - } - - fun onDamageRoll(id: String): Roll { - val action = actionRepository.find(argument.name)?.firstOrNull { it.title == id } - return actionRoll( - attack = action, - throws = action?.damage, - ) - } - - fun initiativeRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_title_initiative, - relatedRes = R.string.character_sheet_stat_dexterity, - masteryLevel = null, - abilityValue = model.dexterity, - property = Property.INITIATIVE, - ) - - fun strengthRoll(): Roll = statRoll( - abilityRes = R.string.character_sheet_stat_strength, - abilityValue = model.strength, - property = Property.STRENGTH, - ) - - fun dexterityRoll(): Roll = statRoll( - abilityRes = R.string.character_sheet_stat_dexterity, - abilityValue = model.dexterity, - property = Property.DEXTERITY, - ) - - fun constitutionRoll(): Roll = statRoll( - abilityRes = R.string.character_sheet_stat_constitution, - abilityValue = model.constitution, - property = Property.CONSTITUTION, - ) - - fun intelligenceRoll(): Roll = statRoll( - abilityRes = R.string.character_sheet_stat_intelligence, - abilityValue = model.intelligence, - property = Property.INTELLIGENCE, - ) - - fun wisdomRoll(): Roll = statRoll( - abilityRes = R.string.character_sheet_stat_wisdom, - abilityValue = model.wisdom, - property = Property.WISDOM, - ) - - fun charismaRoll(): Roll = statRoll( - abilityRes = R.string.character_sheet_stat_charisma, - abilityValue = model.charisma, - property = Property.CHARISMA, - ) - - fun strengthSavingThrowsRoll(): Roll = savingThrowRoll( - abilityRes = R.string.character_sheet_stat_strength, - abilityValue = model.strength, - masteryLevel = model.strengthSavingThrows, - property = Property.STRENGTH_SAVING_THROW, - ) - - fun dexteritySavingThrowsRoll(): Roll = savingThrowRoll( - abilityRes = R.string.character_sheet_stat_dexterity, - abilityValue = model.dexterity, - masteryLevel = model.dexteritySavingThrows, - property = Property.DEXTERITY_SAVING_THROW, - ) - - fun constitutionSavingThrowsRoll(): Roll = savingThrowRoll( - abilityRes = R.string.character_sheet_stat_constitution, - abilityValue = model.constitution, - masteryLevel = model.constitutionSavingThrows, - property = Property.CONSTITUTION_SAVING_THROW, - ) - - fun intelligenceSavingThrowsRoll(): Roll = savingThrowRoll( - abilityRes = R.string.character_sheet_stat_intelligence, - abilityValue = model.intelligence, - masteryLevel = model.intelligenceSavingThrows, - property = Property.INTELLIGENCE_SAVING_THROW, - ) - - fun wisdomSavingThrowsRoll(): Roll = savingThrowRoll( - abilityRes = R.string.character_sheet_stat_wisdom, - abilityValue = model.wisdom, - masteryLevel = model.wisdomSavingThrows, - property = Property.WISDOM_SAVING_THROW, - ) - - fun charismaSavingThrowsRoll(): Roll = savingThrowRoll( - abilityRes = R.string.character_sheet_stat_charisma, - abilityValue = model.charisma, - masteryLevel = model.charismaSavingThrows, - property = Property.CHARISMA_SAVING_THROW, - ) - - fun acrobaticsRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_acrobatics, - relatedRes = R.string.character_sheet_stat_dexterity, - abilityValue = model.dexterity, - masteryLevel = model.acrobatics, - property = Property.ACROBATICS, - ) - - fun animalHandlingRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_animal_handling, - relatedRes = R.string.character_sheet_stat_wisdom, - abilityValue = model.wisdom, - masteryLevel = model.animalHandling, - property = Property.ANIMAL_HANDLING, - ) - - fun arcanaRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_arcana, - relatedRes = R.string.character_sheet_stat_intelligence, - abilityValue = model.intelligence, - masteryLevel = model.arcana, - property = Property.ARCANA, - ) - - fun athleticsRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_athletics, - relatedRes = R.string.character_sheet_stat_strength, - abilityValue = model.strength, - masteryLevel = model.athletics, - property = Property.ATHLETICS, - ) - - fun deceptionRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_deception, - relatedRes = R.string.character_sheet_stat_charisma, - abilityValue = model.charisma, - masteryLevel = model.deception, - property = Property.DECEPTION, - ) - - fun historyRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_history, - relatedRes = R.string.character_sheet_stat_intelligence, - abilityValue = model.intelligence, - masteryLevel = model.history, - property = Property.HISTORY, - ) - - fun insightRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_insight, - relatedRes = R.string.character_sheet_stat_wisdom, - abilityValue = model.wisdom, - masteryLevel = model.insight, - property = Property.INSIGHT, - ) - - fun intimidationRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_intimidation, - relatedRes = R.string.character_sheet_stat_charisma, - abilityValue = model.charisma, - masteryLevel = model.intimidation, - property = Property.INTIMIDATION, - ) - - fun investigationRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_investigation, - relatedRes = R.string.character_sheet_stat_intelligence, - abilityValue = model.intelligence, - masteryLevel = model.investigation, - property = Property.INVESTIGATION, - ) - - fun medicineRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_medicine, - relatedRes = R.string.character_sheet_stat_wisdom, - abilityValue = model.wisdom, - masteryLevel = model.medicine, - property = Property.MEDICINE, - ) - - fun natureRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_nature, - relatedRes = R.string.character_sheet_stat_intelligence, - abilityValue = model.intelligence, - masteryLevel = model.nature, - property = Property.NATURE, - ) - - fun perceptionRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_perception, - relatedRes = R.string.character_sheet_stat_wisdom, - abilityValue = model.wisdom, - masteryLevel = model.perception, - property = Property.PERCEPTION, - ) - - fun performanceRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_performance, - relatedRes = R.string.character_sheet_stat_charisma, - abilityValue = model.charisma, - masteryLevel = model.performance, - property = Property.PERFORMANCE, - ) - - fun persuasionRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_persuasion, - relatedRes = R.string.character_sheet_stat_charisma, - abilityValue = model.charisma, - masteryLevel = model.persuasion, - property = Property.PERSUASION, - ) - - fun religionRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_religion, - relatedRes = R.string.character_sheet_stat_intelligence, - abilityValue = model.intelligence, - masteryLevel = model.religion, - property = Property.RELIGION, - ) - - fun sleightOfHandRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_sleight_of_hand, - relatedRes = R.string.character_sheet_stat_dexterity, - abilityValue = model.dexterity, - masteryLevel = model.sleightOfHand, - property = Property.SLEIGHT_OF_HAND, - ) - - fun stealthRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_stealth, - relatedRes = R.string.character_sheet_stat_dexterity, - abilityValue = model.dexterity, - masteryLevel = model.stealth, - property = Property.STEALTH, - ) - - fun survivalRoll(): Roll = abilityRoll( - abilityRes = R.string.character_sheet_proficiency_survival, - relatedRes = R.string.character_sheet_stat_wisdom, - abilityValue = model.wisdom, - masteryLevel = model.survival, - property = Property.SURVIVAL, - ) - - //////////////////////////////////////// - // region: Helpers - - private fun statRoll( - abilityRes: Int, - abilityValue: Int, - property: Property, - ): Roll { - val ability = context.getString(abilityRes) - // get the alteration for a given player - val alterations = alterationRepository.getStatus( - character = model.name, - property = property, - ) - // check if any alteration give us advantage or disadvantage - val advantage = alterations.any { it.advantage } - val disadvantage = alterations.any { it.disadvantage } - val fail = alterations.any { it.fail } - - return Roll( - title = context.getString(R.string.dice_roll_check_title, ability.uppercase()), - highlight = ability, - dices = listOf( - d20( - title = context.getString(R.string.dice_roll_check_detail, ability), - advantage = advantage, - disadvantage = disadvantage, - fail = fail, - ), - ) + alterations.map { it.dices }.flatten(), - bonus = listOf( - Roll.Bonus( - title = context.getString(R.string.dice_roll_bonus_detail, ability), - value = abilityValue.modifier - ) - ) + alterations.map { it.bonus }.flatten(), - ) - } - - private fun savingThrowRoll( - @StringRes abilityRes: Int, - abilityValue: Int, - masteryLevel: Int, - masteryValue: Int = model.proficiency, - property: Property, - ): Roll { - val ability = context.getString(abilityRes) - - // get the alteration for a given player - val alterations = alterationRepository.getStatus( - character = model.name, - property = property, - ) - // check if any alteration give us advantage or disadvantage - val advantage = alterations.any { it.advantage } - val disadvantage = alterations.any { it.disadvantage } - val fail = alterations.any { it.fail } - - return Roll( - title = context.getString(R.string.dice_roll_saving_throw_title, ability.uppercase()), - highlight = ability, - dices = listOf( - d20( - title = context.getString(R.string.dice_roll_saving_throw_detail, ability), - advantage = advantage, - disadvantage = disadvantage, - fail = fail, - ), - ) + alterations.map { it.dices }.flatten(), - bonus = listOf( - Roll.Bonus( - title = context.getString( - if (masteryLevel == 2) R.string.dice_roll_mastery_expertise else R.string.dice_roll_mastery_proficiency, - context.getString(R.string.dice_roll_mastery_saving_throw) - ), - value = masteryValue * masteryLevel, - ), - Roll.Bonus( - title = context.getString(R.string.dice_roll_bonus_detail, ability), - value = abilityValue.modifier - ), - ) + alterations.map { it.bonus }.flatten(), - ) - } - - private fun abilityRoll( - @StringRes abilityRes: Int, - @StringRes relatedRes: Int, - abilityValue: Int, - masteryLevel: Int?, - masteryValue: Int = model.proficiency, - property: Property, - ): Roll { - val ability = context.getString(abilityRes) - val related = context.getString(relatedRes) - - // get the alteration for a given player - val alterations = alterationRepository.getStatus( - character = model.name, - property = property, - ) - // check if any alteration give us advantage or disadvantage - val advantage = alterations.any { it.advantage } - val disadvantage = alterations.any { it.disadvantage } - val fail = alterations.any { it.fail } - - return Roll( - title = context.getString(R.string.dice_roll_check_title, ability.uppercase()), - highlight = ability, - dices = listOf( - d20( - title = context.getString(R.string.dice_roll_check_detail, ability), - advantage = advantage, - disadvantage = disadvantage, - fail = fail, - ), - ) + alterations.map { it.dices }.flatten(), - bonus = (masteryLevel?.let { - listOf( - Roll.Bonus( - title = context.getString( - if (masteryLevel == 2) R.string.dice_roll_mastery_expertise else R.string.dice_roll_mastery_proficiency, - ability - ), - value = masteryValue * masteryLevel, - ) - ) - } ?: emptyList()) + listOf( - Roll.Bonus( - title = context.getString(R.string.dice_roll_bonus_detail, related), - value = abilityValue.modifier - ) - ) + alterations.map { it.bonus }.flatten(), - ) - } - - private fun actionRoll( - attack: Attack?, - throws: Attack.Throw?, - ): Roll { - // build the title - val title = context.getString( - when (attack?.type) { - else -> when (throws === attack?.hit) { - true -> R.string.dice_roll_attack_hit_title - else -> R.string.dice_roll_attack_damage_title - } - }, - attack?.title, - ) - // get the alteration for roll and a given player - val alterations = when (attack?.type) { - Attack.Type.PHYSICAL_MELEE_ATTACK -> { - alterationRepository.getStatus( - character = model.name, - property = when (throws === attack.hit) { - true -> Property.PHYSICAL_MELEE_ATTACK - else -> Property.PHYSICAL_MELEE_DAMAGE - }, - ) - } - - Attack.Type.PHYSICAL_RANGE_ATTACK -> { - alterationRepository.getStatus( - character = model.name, - property = when (throws === attack.hit) { - true -> Property.PHYSICAL_RANGE_ATTACK - else -> Property.PHYSICAL_RANGE_DAMAGE - }, - ) - } - - else -> { - emptyList() + val actionRequest = async { + try { + actionRepository.fetchActions() + } catch (exception: Exception) { + Log.e(TAG, exception.message, exception) } } - // check if any alteration give us advantage or disadvantage - val advantage = alterations.any { it.advantage } - val disadvantage = alterations.any { it.disadvantage } - val fail = alterations.any { it.fail } - // build the roll - return Roll( - title = title, - highlight = attack?.title, - dices = listOf( - Roll.Dice( - title = attack?.title, - advantage = advantage, - disadvantage = disadvantage, - fail = fail, - count = throws?.amount ?: 1, - faces = throws?.faces ?: 20, - ) - ) + alterations.map { it.dices }.flatten(), - bonus = (throws?.modifier - ?.mapNotNull { - when (it) { - Property.PROFICIENCY -> { - Roll.Bonus( - title = context.getString(R.string.dice_roll_proficiency_bonus), - value = model.proficiency, - ) - } - - Property.STRENGTH -> { - val related = context.getString(R.string.character_sheet_stat_strength) - Roll.Bonus( - title = context.getString(R.string.dice_roll_bonus_detail, related), - value = model.strength.modifier, - ) - } - - Property.DEXTERITY -> { - val related = context.getString(R.string.character_sheet_stat_dexterity) - Roll.Bonus( - title = context.getString(R.string.dice_roll_bonus_detail, related), - value = model.dexterity.modifier, - ) - } - - Property.CONSTITUTION -> { - val related = - context.getString(R.string.character_sheet_stat_constitution) - Roll.Bonus( - title = context.getString(R.string.dice_roll_bonus_detail, related), - value = model.constitution.modifier, - ) - } - - Property.INTELLIGENCE -> { - val related = - context.getString(R.string.character_sheet_stat_intelligence) - Roll.Bonus( - title = context.getString(R.string.dice_roll_bonus_detail, related), - value = model.intelligence.modifier, - ) - } - - Property.WISDOM -> { - val related = context.getString(R.string.character_sheet_stat_wisdom) - Roll.Bonus( - title = context.getString(R.string.dice_roll_bonus_detail, related), - value = model.wisdom.modifier, - ) - } - - Property.CHARISMA -> { - val related = context.getString(R.string.character_sheet_stat_charisma) - Roll.Bonus( - title = context.getString(R.string.dice_roll_bonus_detail, related), - value = model.charisma.modifier, - ) - } - - else -> null - } - } ?: emptyList()) + alterations.map { it.bonus }.flatten(), - ) + val spellRequest = async { + try { + spellRepository.fetchSpells() + } catch (exception: Exception) { + Log.e(TAG, exception.message, exception) + } + } + awaitAll(characterRequest, statusRequest, alterationRequest, actionRequest, spellRequest) + _isLoading.value = false } // endregion diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Action.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Action.kt deleted file mode 100644 index 973420e..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Action.kt +++ /dev/null @@ -1,126 +0,0 @@ -package com.pixelized.rplexicon.ui.screens.character.composable - -import android.content.res.Configuration.UI_MODE_NIGHT_NO -import android.content.res.Configuration.UI_MODE_NIGHT_YES -import androidx.annotation.DrawableRes -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CutCornerShape -import androidx.compose.material3.Button -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.dp -import com.pixelized.rplexicon.R -import com.pixelized.rplexicon.ui.theme.LexiconTheme -import com.pixelized.rplexicon.utilitary.extentions.ddBorder - -@Stable -data class ActionsUio( - val title: String, - @DrawableRes val hit: Int?, - @DrawableRes val damage: Int?, -) - -@Composable -fun Action( - modifier: Modifier = Modifier, - padding: PaddingValues = PaddingValues(start = 16.dp, end = 8.dp, top = 4.dp, bottom = 4.dp), - action: ActionsUio, - onHit: (id: String) -> Unit, - onDamage: (id: String) -> Unit, -) { - Row( - modifier = modifier - .ddBorder( - outline = remember { CutCornerShape(size = 16.dp) }, - inner = RectangleShape, - ) - .padding(paddingValues = padding), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Text( - modifier = Modifier.weight(1f), - style = MaterialTheme.typography.bodyMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - text = action.title, - ) - - action.hit?.let { - Button( - contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp), - onClick = { onHit(action.title) }, - ) { - Icon( - modifier = Modifier.size(size = 24.dp), - painter = painterResource(id = it), - contentDescription = null, - ) - } - } - - action.damage?.let { - Button( - contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp), - onClick = { onDamage(action.title) }, - ) { - Icon( - modifier = Modifier.size(size = 24.dp), - painter = painterResource(id = it), - contentDescription = null, - ) - } - } - } -} - -@Composable -@Preview(uiMode = UI_MODE_NIGHT_NO) -@Preview(uiMode = UI_MODE_NIGHT_YES) -private fun ActionPreview( - @PreviewParameter(ActionPreviewProvider::class) preview: ActionsUio -) { - LexiconTheme { - Surface { - Action( - modifier = Modifier.fillMaxWidth(), - action = preview, - onHit = { }, - onDamage = { }, - ) - } - } -} - -private class ActionPreviewProvider : PreviewParameterProvider { - override val values: Sequence = sequenceOf( - ActionsUio( - title = "Hache d'arme", - hit = R.drawable.ic_d20_24, - damage = R.drawable.ic_d8_24, - ), - ActionsUio( - title = "Explosion occulte", - hit = R.drawable.ic_d20_24, - damage = R.drawable.ic_d10_24, - ) - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Counter.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Counter.kt deleted file mode 100644 index 083bd23..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Counter.kt +++ /dev/null @@ -1,163 +0,0 @@ -package com.pixelized.rplexicon.ui.screens.character.composable - -import android.content.res.Configuration.UI_MODE_NIGHT_NO -import android.content.res.Configuration.UI_MODE_NIGHT_YES -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CutCornerShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.dp -import com.pixelized.rplexicon.ui.theme.LexiconTheme -import com.pixelized.rplexicon.utilitary.extentions.ddBorder -import com.pixelized.rplexicon.utilitary.extentions.lexicon - -@Stable -data class CounterUio( - val title: String, - val value: Int, - val max: Int?, -) - -@Composable -fun Counter( - modifier: Modifier = Modifier, - padding: PaddingValues = PaddingValues(start = 16.dp, end = 8.dp, top = 4.dp, bottom = 4.dp), - counter: CounterUio, -) { - Box( - modifier = modifier.ddBorder( - outline = remember { CutCornerShape(size = 16.dp) }, - inner = RectangleShape, - ), - contentAlignment = Alignment.CenterStart, - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .heightIn(min = 40.dp) - .padding(paddingValues = padding), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp), - ) { - Text( - modifier = Modifier.weight(1f), - style = MaterialTheme.typography.bodyMedium, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - text = counter.title, - ) - if (counter.max != null && counter.max <= 8) { - repeat(counter.max) { index -> - Marker( - filled = (counter.max - index) > counter.value, - ) - } - } else { - Text( - style = MaterialTheme.typography.bodyMedium, - fontWeight = FontWeight.Bold, - text = "${counter.value}", - ) - counter.max?.let { max -> - Text( - style = MaterialTheme.typography.labelSmall, - text = "/", - ) - Text( - style = MaterialTheme.typography.bodyMedium, - text = "$max", - ) - } - } - } - } -} - -@Composable -private fun Marker( - modifier: Modifier = Modifier, - filled: Boolean, -) { - Surface( - modifier = modifier, - tonalElevation = 8.dp, - border = BorderStroke(width = 1.dp, color = MaterialTheme.lexicon.colorScheme.handle), - ) { - Box( - modifier = Modifier - .padding(all = 4.dp) - .size(16.dp) - .background( - color = when (filled) { - true -> MaterialTheme.colorScheme.primary - else -> Color.Transparent - } - ) - ) - } -} - -@Composable -@Preview(uiMode = UI_MODE_NIGHT_NO) -@Preview(uiMode = UI_MODE_NIGHT_YES) -private fun CounterPreview( - @PreviewParameter(CounterPreviewProvider::class) preview: CounterUio -) { - LexiconTheme { - Surface { - Counter(counter = preview) - } - } -} - -private class CounterPreviewProvider : PreviewParameterProvider { - override val values: Sequence = sequenceOf( - CounterUio( - title = "Rage", - value = 2, - max = 2, - ), - CounterUio( - title = "Dé de vie", - value = 0, - max = 1, - ), - CounterUio( - title = "Sort de niveau 1", - value = 3, - max = 8, - ), - CounterUio( - title = "Point de vie", - value = 15, - max = null, - ), - CounterUio( - title = "Point de vie", - value = 15, - max = 25, - ), - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Weapon.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/AttackItem.kt similarity index 75% rename from app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Weapon.kt rename to app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/AttackItem.kt index a35749f..df052dc 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Weapon.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/AttackItem.kt @@ -1,8 +1,9 @@ -package com.pixelized.rplexicon.ui.screens.character.composable +package com.pixelized.rplexicon.ui.screens.character.composable.actions import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -19,6 +20,7 @@ import androidx.compose.runtime.Stable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -31,12 +33,12 @@ import com.pixelized.rplexicon.model.Attack import com.pixelized.rplexicon.ui.theme.LexiconTheme @Stable -data class WeaponUio( +data class AttackUio( val id: Attack? = null, @DrawableRes val icon: Int, val name: String, - val type: String, - val range: String, + @StringRes val type: Int, + val range: String?, val hit: Dice?, val damage: Dice?, ) { @@ -48,9 +50,9 @@ data class WeaponUio( } @Composable -fun Weapon( +fun Attack( modifier: Modifier = Modifier, - weapon: WeaponUio, + weapon: AttackUio, onHit: (String) -> Unit, onDamage: (String) -> Unit, ) { @@ -78,25 +80,28 @@ fun Weapon( fontStyle = FontStyle.Italic, overflow = TextOverflow.Ellipsis, maxLines = 1, - text = weapon.type, - ) - Text( - style = MaterialTheme.typography.labelSmall, - fontStyle = FontStyle.Italic, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - text = weapon.range, + text = stringResource(id = weapon.type), ) + weapon.range?.let { range -> + Text( + style = MaterialTheme.typography.labelSmall, + fontStyle = FontStyle.Italic, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = range, + ) + } } weapon.hit?.let { dice -> Column( modifier = Modifier - .sizeIn(minWidth = 32.dp) + .sizeIn(minWidth = 42.dp) .clickable { weapon.name.let(onHit) }, horizontalAlignment = Alignment.CenterHorizontally, ) { Icon( painter = painterResource(id = dice.icon), + tint = MaterialTheme.colorScheme.primary, contentDescription = null, ) Text( @@ -114,6 +119,7 @@ fun Weapon( ) { Icon( painter = painterResource(id = dice.icon), + tint = MaterialTheme.colorScheme.primary, contentDescription = null, ) Text( @@ -129,11 +135,11 @@ fun Weapon( @Preview(uiMode = UI_MODE_NIGHT_NO) @Preview(uiMode = UI_MODE_NIGHT_YES) private fun WeaponPreview( - @PreviewParameter(WeaponPreviewProvider::class) preview: WeaponUio, + @PreviewParameter(WeaponPreviewProvider::class) preview: AttackUio, ) { LexiconTheme { Surface { - Weapon( + Attack( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -145,23 +151,23 @@ private fun WeaponPreview( } } -private class WeaponPreviewProvider : PreviewParameterProvider { - override val values: Sequence = sequenceOf( - WeaponUio( +private class WeaponPreviewProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf( + AttackUio( icon = R.drawable.ic_crossed_swords_24, name = "Dagger", - type = "Melee weapon", + type = R.string.attack_type_melee, range = "5 ft reach", - hit = WeaponUio.Dice(icon = R.drawable.ic_d20_24, label = "1d20"), - damage = WeaponUio.Dice(icon = R.drawable.ic_d8_24, label = "1d8"), + hit = AttackUio.Dice(icon = R.drawable.ic_d20_24, label = "1d20"), + damage = AttackUio.Dice(icon = R.drawable.ic_d8_24, label = "1d8"), ), - WeaponUio( + AttackUio( icon = R.drawable.ic_pocket_bow_24, name = "Long bow", - type = "Ranged weapon", + type = R.string.attack_type_range, range = "30 ft reach", - hit = WeaponUio.Dice(icon = R.drawable.ic_d20_24, label = "1d20+5"), - damage = WeaponUio.Dice(icon = R.drawable.ic_d8_24, label = "1d8+3"), + hit = AttackUio.Dice(icon = R.drawable.ic_d20_24, label = "1d20+5"), + damage = AttackUio.Dice(icon = R.drawable.ic_d8_24, label = "1d8+3"), ), ) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellHeader.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellHeader.kt new file mode 100644 index 0000000..5974d25 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellHeader.kt @@ -0,0 +1,141 @@ +package com.pixelized.rplexicon.ui.screens.character.composable.actions + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Divider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.theme.LexiconTheme +import com.pixelized.rplexicon.utilitary.extentions.lexicon + +@Stable +data class SpellHeaderUio( + val level: Int, + val count: Count?, +) { + @Stable + data class Count( + val value: Int, + val max: Int?, + ) +} + +@Composable +fun SpellHeader( + modifier: Modifier = Modifier, + header: SpellHeaderUio, +) { + Box( + modifier = modifier + .background(color = MaterialTheme.colorScheme.surface) + .padding(horizontal = 16.dp) + .heightIn(min = 32.dp), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .align(alignment = Alignment.Center), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + modifier = Modifier + .weight(1f) + .alignByBaseline(), + style = MaterialTheme.typography.titleMedium, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = stringResource( + id = when (header.level) { + 0 -> R.string.character_sheet_action_spell_level_0 + else -> R.string.character_sheet_action_spell_level_X + }, + header.level + ).let { label -> + AnnotatedString( + text = label, + spanStyles = listOf( + AnnotatedString.Range( + item = MaterialTheme.lexicon.typography.bodyDropCapSpan, + start = 0, + end = Integer.min(1, label.length), + ) + ) + ) + }, + ) + header.count?.let { count -> + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.titleMedium, + text = "${count.value}", + ) + count.max?.let { max -> + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.labelSmall, + text = "/", + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.bodySmall, + text = "$max", + ) + } + } + } + + Divider( + modifier = Modifier.align(alignment = Alignment.BottomCenter), + color = MaterialTheme.lexicon.colorScheme.placeholder, + ) + } +} + +@Composable +@Preview(uiMode = UI_MODE_NIGHT_NO) +@Preview(uiMode = UI_MODE_NIGHT_YES) +private fun SpellHeaderPreview( + @PreviewParameter(SpellHeaderPreviewProvider::class) preview: SpellHeaderUio +) { + LexiconTheme { + Surface { + SpellHeader( + header = preview, + ) + } + } +} + +private class SpellHeaderPreviewProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf( + SpellHeaderUio( + level = 0, + count = null, + ), + SpellHeaderUio( + level = 2, + count = SpellHeaderUio.Count(value = 2, max = 3), + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellItem.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellItem.kt new file mode 100644 index 0000000..8c66c18 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellItem.kt @@ -0,0 +1,260 @@ +package com.pixelized.rplexicon.ui.screens.character.composable.actions + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.annotation.DrawableRes +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.model.Spell +import com.pixelized.rplexicon.ui.theme.LexiconTheme +import com.pixelized.rplexicon.utilitary.extentions.local.icon + +@Stable +data class SpellUio( + @DrawableRes val icon: Int, + val name: String, + val translated: String, + val castingTime: String, + val range: String, + val duration: String, + val hit: Dice?, + val effect: Dice?, + val changeWithLevel: Boolean, +) { + class Dice( + @DrawableRes val icon: Int, + val label: String, + ) +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun Spell( + modifier: Modifier = Modifier, + spell: SpellUio, + onClick: (String) -> Unit, + onHit: (String) -> Unit, + onEffect: (String) -> Unit, + onCast: (String) -> Unit, +) { + Row( + modifier = modifier.clickable { onClick(spell.name) }, + horizontalArrangement = Arrangement.spacedBy(space = 10.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier.size(size = 28.dp), + painter = painterResource(id = spell.icon), + contentDescription = null, + ) + + Column( + modifier = Modifier.weight(weight = 1f), + ) { + FlowRow( + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + ) { + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = spell.name, + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.labelSmall, + fontStyle = FontStyle.Italic, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = spell.translated, + ) + } + + Row( + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + ) { + Text( + style = MaterialTheme.typography.labelMedium, + fontStyle = FontStyle.Italic, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = spell.castingTime, + ) + + Text( + style = MaterialTheme.typography.labelMedium, + fontStyle = FontStyle.Italic, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = "-", + ) + + Text( + style = MaterialTheme.typography.labelMedium, + fontStyle = FontStyle.Italic, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = spell.duration, + ) + } + + Text( + style = MaterialTheme.typography.labelMedium, + fontStyle = FontStyle.Italic, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = spell.range, + ) + } + + spell.hit?.let { + Column( + modifier = Modifier + .sizeIn(minWidth = 42.dp) + .clickable { spell.name.let(onHit) }, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + painter = painterResource(id = it.icon), + tint = MaterialTheme.colorScheme.primary, + contentDescription = null, + ) + Text( + style = MaterialTheme.typography.labelSmall, + text = it.label, + ) + } + } + spell.effect?.let { + if (spell.changeWithLevel) { + OutlinedButton( + border = BorderStroke( + width = 1.dp, + color = MaterialTheme.colorScheme.primary, + ), + onClick = { onCast(spell.name) }, + ) { + Text( + text = stringResource(id = R.string.character_sheet_action_spell_cast), + ) + } + } else { + Column( + modifier = Modifier + .sizeIn(minWidth = 42.dp) + .clickable { spell.name.let(onEffect) }, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + painter = painterResource(id = it.icon), + tint = MaterialTheme.colorScheme.primary, + contentDescription = null, + ) + Text( + style = MaterialTheme.typography.labelSmall, + text = it.label, + ) + } + } + } + } +} + +@Composable +@Preview(uiMode = UI_MODE_NIGHT_NO) +@Preview(uiMode = UI_MODE_NIGHT_YES) +private fun SpellPreview( + @PreviewParameter(SpellPreviewProvider::class) preview: SpellUio, +) { + LexiconTheme { + Surface { + Spell( + modifier = Modifier.padding(horizontal = 16.dp), + spell = preview, + onClick = { }, + onHit = { }, + onEffect = { }, + onCast = { }, + ) + } + } +} + +private class SpellPreviewProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf( + SpellUio( + icon = Spell.School.EVOCATION.icon, + name = "Trait de feu", + translated = "Fire Bolt", + castingTime = "1 action", + range = "36 mêtres", + duration = "instantanée", + hit = SpellUio.Dice( + icon = R.drawable.ic_d20_24, + label = "1d20+6", + ), + effect = SpellUio.Dice( + icon = R.drawable.ic_d10_24, + label = "1d10", + ), + changeWithLevel = false + ), + SpellUio( + icon = Spell.School.ENCHANTMENT.icon, + name = "Moquerie cruelle", + translated = "Vicious Mockery", + castingTime = "1 action", + range = "18 mêtres", + duration = "instantanée", + hit = null, + effect = SpellUio.Dice( + icon = R.drawable.ic_d10_24, + label = "1d4", + ), + changeWithLevel = false + ), + SpellUio( + icon = Spell.School.EVOCATION.icon, + name = "Soins", + translated = "Cure Wounds", + castingTime = "1 action", + range = "contact", + duration = "instantanée", + hit = null, + effect = SpellUio.Dice( + icon = R.drawable.ic_d8_24, + label = "1d8+3", + ), + changeWithLevel = true, + ), + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellLevelItem.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellLevelItem.kt new file mode 100644 index 0000000..9f1657e --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/SpellLevelItem.kt @@ -0,0 +1,126 @@ +package com.pixelized.rplexicon.ui.screens.character.composable.actions + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.annotation.DrawableRes +import androidx.compose.foundation.clickable +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.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.theme.LexiconTheme + +@Stable +data class SpellLevelUio( + val spell: String, + val level: Int, + val remaining: Int?, + val max: Int?, + @DrawableRes val icon: Int, + val value: String, +) + +@Composable +fun SpellLevelItem( + modifier: Modifier = Modifier, + spell: SpellLevelUio, + onSpellCast: (id: String, level: Int) -> Unit +) { + Row( + modifier = modifier + .clickable { onSpellCast(spell.spell, spell.level) } + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Column { + Text( + style = MaterialTheme.typography.bodyMedium, + text = stringResource( + id = R.string.spell_level_chooser_label, + spell.level + ) + ) + Row( + horizontalArrangement = Arrangement.spacedBy(space = 1.dp) + ) { + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.bodySmall, + text = stringResource(id = R.string.spell_level_chooser_available) + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.bodySmall, + fontWeight = FontWeight.Bold, + text = "${spell.remaining ?: 0}", + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.labelSmall, + text = "/", + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.labelSmall, + text = "${spell.max ?: 0}", + ) + } + } + Row( + modifier = Modifier.padding(start = 8.dp), + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(id = spell.icon), + tint = MaterialTheme.colorScheme.primary, + contentDescription = null, + ) + Text( + style = MaterialTheme.typography.labelSmall, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + text = spell.value + ) + } + } +} + +@Composable +@Preview(uiMode = UI_MODE_NIGHT_NO) +@Preview(uiMode = UI_MODE_NIGHT_YES) +private fun SpellLevelPreview() { + LexiconTheme { + Surface { + SpellLevelItem( + spell = SpellLevelUio( + spell = "", + level = 1, + icon = R.drawable.ic_d8_24, + remaining = 1, + max = 2, + value = "1d8", + ), + onSpellCast = { _, _ -> }, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/CharacterSheetHeader.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/CharacterSheetHeader.kt new file mode 100644 index 0000000..f0589aa --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/CharacterSheetHeader.kt @@ -0,0 +1,64 @@ +package com.pixelized.rplexicon.ui.screens.character.composable.character + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Surface +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.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberCharacterHeaderStatePreview +import com.pixelized.rplexicon.ui.theme.LexiconTheme + +@Stable +data class CharacterSheetHeaderUio( + val armorClass: LabelPointUio, + val hitPoint: LabelPointUio, + val speed: LabelPointUio, +) + +@Composable +fun CharacterSheetHeader( + modifier: Modifier = Modifier, + header: State, +) { + Surface( + modifier = modifier, + shadowElevation = 4.dp, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.spacedBy( + space = 16.dp, + alignment = Alignment.CenterHorizontally + ), + ) { + header.value?.armorClass?.let { LabelPoint(label = it) } + header.value?.hitPoint?.let { LabelPoint(label = it) } + header.value?.speed?.let { LabelPoint(label = it) } + } + } +} + +@Composable +@Preview(uiMode = UI_MODE_NIGHT_NO) +@Preview(uiMode = UI_MODE_NIGHT_YES) +fun CharacterSheetHeaderPreview() { + LexiconTheme { + Surface { + CharacterSheetHeader( + header = rememberCharacterHeaderStatePreview() + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/LabelPoint.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/LabelPoint.kt similarity index 97% rename from app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/LabelPoint.kt rename to app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/LabelPoint.kt index a946c40..cb83336 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/LabelPoint.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/LabelPoint.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.ui.screens.character.composable +package com.pixelized.rplexicon.ui.screens.character.composable.character import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/MasteryCircle.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/MasteryCircle.kt similarity index 95% rename from app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/MasteryCircle.kt rename to app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/MasteryCircle.kt index 4e9b38d..7c3c933 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/MasteryCircle.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/MasteryCircle.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.ui.screens.character.composable +package com.pixelized.rplexicon.ui.screens.character.composable.character import androidx.compose.foundation.background import androidx.compose.foundation.border diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Proficiency.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/Proficiency.kt similarity index 99% rename from app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Proficiency.kt rename to app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/Proficiency.kt index c9c5aee..37c7be6 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Proficiency.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/Proficiency.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.ui.screens.character.composable +package com.pixelized.rplexicon.ui.screens.character.composable.character import android.content.res.Configuration import androidx.annotation.StringRes diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Stat.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/Stat.kt similarity index 97% rename from app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Stat.kt rename to app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/Stat.kt index cf52e3e..413cccc 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/Stat.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/character/Stat.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.ui.screens.character.composable +package com.pixelized.rplexicon.ui.screens.character.composable.character import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberAttackListStatePreview.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberAttackListStatePreview.kt new file mode 100644 index 0000000..6a94a59 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberAttackListStatePreview.kt @@ -0,0 +1,46 @@ +package com.pixelized.rplexicon.ui.screens.character.composable.preview + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.screens.character.composable.actions.AttackUio + +@Stable +@Composable +fun rememberAttackListStatePreview(): State> = remember { + mutableStateOf( + listOf( + AttackUio( + icon = R.drawable.ic_crossed_swords_24, + name = "Dagger", + type = R.string.attack_type_melee, + range = "5 ft reach", + hit = AttackUio.Dice( + icon = R.drawable.ic_d20_24, + label = "1d20", + ), + damage = AttackUio.Dice( + icon = R.drawable.ic_d8_24, + label = "1d8", + ), + ), + AttackUio( + icon = R.drawable.ic_pocket_bow_24, + name = "Long bow", + type = R.string.attack_type_range, + range = "30 ft reach", + hit = AttackUio.Dice( + icon = R.drawable.ic_d20_24, + label = "1d20+5", + ), + damage = AttackUio.Dice( + icon = R.drawable.ic_d8_24, + label = "1d8+3" + ), + ), + ) + ) +} diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberCharacterHeaderStatePreview.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberCharacterHeaderStatePreview.kt new file mode 100644 index 0000000..05104ce --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberCharacterHeaderStatePreview.kt @@ -0,0 +1,32 @@ +package com.pixelized.rplexicon.ui.screens.character.composable.preview + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio +import com.pixelized.rplexicon.ui.screens.character.composable.character.LabelPointUio + +@Composable +@Stable +fun rememberCharacterHeaderStatePreview() = remember { + mutableStateOf( + CharacterSheetHeaderUio( + armorClass = LabelPointUio( + label = R.string.character_sheet_title_ca, + value = "15", + ), + hitPoint = LabelPointUio( + label = R.string.character_sheet_title_hp, + value = "125", + max = "/ 150", + ), + speed = LabelPointUio( + label = R.string.character_sheet_title_speed, + value = "30", + max = "m", + ), + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberCharacterSheetStatePreview.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberCharacterSheetStatePreview.kt new file mode 100644 index 0000000..24bc060 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberCharacterSheetStatePreview.kt @@ -0,0 +1,180 @@ +package com.pixelized.rplexicon.ui.screens.character.composable.preview + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.screens.character.composable.character.LabelPointUio +import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio +import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio +import com.pixelized.rplexicon.ui.screens.character.pages.proficiency.CharacterSheetUio + +@Stable +@Composable +fun rememberCharacterSheetStatePreview() = remember { + mutableStateOf( + CharacterSheetUio( + initiative = LabelPointUio( + label = R.string.character_sheet_title_initiative, + value = "+2", + ), + stats = listOf( + StatUio( + id = StatUio.ID.STRENGTH, + value = 16, + modifier = 3, + ), + StatUio( + id = StatUio.ID.DEXTERITY, + value = 10, + modifier = 0, + ), + StatUio( + id = StatUio.ID.CONSTITUTION, + value = 16, + modifier = 3, + ), + StatUio( + id = StatUio.ID.INTELLIGENCE, + value = 8, + modifier = 1, + ), + StatUio( + id = StatUio.ID.WISDOM, + value = 14, + modifier = 2, + ), + StatUio( + id = StatUio.ID.CHARISMA, + value = 10, + modifier = 0, + ), + ), + savingThrows = listOf( + ProficiencyUio( + id = ProficiencyUio.ID.STRENGTH_SAVING_THROW, + multiplier = 1, + modifier = 5, + ), + ProficiencyUio( + id = ProficiencyUio.ID.DEXTERITY_SAVING_THROW, + multiplier = 0, + modifier = 0, + ), + ProficiencyUio( + id = ProficiencyUio.ID.CONSTITUTION_SAVING_THROW, + multiplier = 1, + modifier = +5, + ), + ProficiencyUio( + id = ProficiencyUio.ID.INTELLIGENCE_SAVING_THROW, + multiplier = 0, + modifier = -1, + ), + ProficiencyUio( + id = ProficiencyUio.ID.WISDOM_SAVING_THROW, + multiplier = 0, + modifier = +2, + ), + ProficiencyUio( + id = ProficiencyUio.ID.CHARISMA_SAVING_THROW, + multiplier = 0, + modifier = +0, + ), + ), + proficiencies = listOf( + ProficiencyUio( + id = ProficiencyUio.ID.ACROBATICS, + multiplier = 0, + modifier = +0, + ), + ProficiencyUio( + id = ProficiencyUio.ID.ANIMAL_HANDLING, + multiplier = 0, + modifier = 2, + ), + ProficiencyUio( + id = ProficiencyUio.ID.ARCANA, + multiplier = 0, + modifier = -1, + ), + ProficiencyUio( + id = ProficiencyUio.ID.ATHLETICS, + multiplier = 1, + modifier = 5, + ), + ProficiencyUio( + id = ProficiencyUio.ID.DECEPTION, + multiplier = 0, + modifier = 0, + ), + ProficiencyUio( + id = ProficiencyUio.ID.HISTORY, + multiplier = 0, + modifier = -1, + ), + ProficiencyUio( + id = ProficiencyUio.ID.INSIGHT, + multiplier = 1, + modifier = 4, + ), + ProficiencyUio( + id = ProficiencyUio.ID.INTIMIDATION, + multiplier = 1, + modifier = 2, + ), + ProficiencyUio( + id = ProficiencyUio.ID.INVESTIGATION, + multiplier = 0, + modifier = -1, + ), + ProficiencyUio( + id = ProficiencyUio.ID.MEDICINE, + multiplier = 0, + modifier = 2, + ), + ProficiencyUio( + id = ProficiencyUio.ID.NATURE, + multiplier = 0, + modifier = -1, + ), + ProficiencyUio( + id = ProficiencyUio.ID.PERCEPTION, + multiplier = 1, + modifier = 4, + ), + ProficiencyUio( + id = ProficiencyUio.ID.PERFORMANCE, + multiplier = 0, + modifier = 0, + ), + ProficiencyUio( + id = ProficiencyUio.ID.PERSUASION, + multiplier = 0, + modifier = 0, + ), + ProficiencyUio( + id = ProficiencyUio.ID.RELIGION, + multiplier = 0, + modifier = -1, + ), + ProficiencyUio( + id = ProficiencyUio.ID.SLEIGHT_OF_HAND, + multiplier = 0, + modifier = 0, + ), + ProficiencyUio( + id = ProficiencyUio.ID.STEALTH, + multiplier = 0, + modifier = 0, + ), + ProficiencyUio( + id = ProficiencyUio.ID.SURVIVAL, + multiplier = 1, + modifier = 4, + ), + ), + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberSpellLevelChooserState.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberSpellLevelChooserState.kt new file mode 100644 index 0000000..f7d331f --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberSpellLevelChooserState.kt @@ -0,0 +1,45 @@ +package com.pixelized.rplexicon.ui.screens.character.composable.preview + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellLevelUio +import com.pixelized.rplexicon.ui.screens.character.pages.chooser.SpellChooserUio + +@Composable +@Stable +fun rememberSpellLevelChooserState() = remember { + mutableStateOf( + SpellChooserUio( + name = "Cure wounds", + spells = listOf( + SpellLevelUio( + spell = "", + level = 1, + icon = R.drawable.ic_d8_24, + remaining = 1, + max = 4, + value = "1d8", + ), + SpellLevelUio( + spell = "", + level = 2, + remaining = 1, + max = 3, + icon = R.drawable.ic_d8_24, + value = "1d8 + 1d8", + ), + SpellLevelUio( + spell = "", + level = 3, + remaining = 2, + max = 2, + icon = R.drawable.ic_d8_24, + value = "1d8 + 2d8", + ), + ), + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberSpellListStatePreview.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberSpellListStatePreview.kt new file mode 100644 index 0000000..95cf876 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberSpellListStatePreview.kt @@ -0,0 +1,76 @@ +package com.pixelized.rplexicon.ui.screens.character.composable.preview + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.model.Spell +import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellHeaderUio +import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellUio +import com.pixelized.rplexicon.utilitary.extentions.local.icon + +@Stable +@Composable +fun rememberSpellListStatePreview(): State>>> = remember { + mutableStateOf( + listOf( + SpellHeaderUio( + level = 0, + count = null, + ) to listOf( + SpellUio( + icon = Spell.School.EVOCATION.icon, + name = "Trait de feu", + translated = "Fire Bolt", + castingTime = "1 action", + range = "36 mêtres", + duration = "instantanée", + hit = SpellUio.Dice( + icon = R.drawable.ic_d20_24, + label = "1d20+6", + ), + effect = SpellUio.Dice( + icon = R.drawable.ic_d10_24, + label = "1d10", + ), + changeWithLevel = false + ), + SpellUio( + icon = Spell.School.ENCHANTMENT.icon, + name = "Moquerie cruelle", + translated = "Vicious Mockery", + castingTime = "1 action", + range = "18 mêtres", + duration = "instantanée", + hit = null, + effect = SpellUio.Dice( + icon = R.drawable.ic_d10_24, + label = "1d4", + ), + changeWithLevel = false + ), + ), + SpellHeaderUio( + level = 1, + count = SpellHeaderUio.Count(value = 2, max = 3), + ) to listOf( + SpellUio( + icon = Spell.School.EVOCATION.icon, + name = "Soins", + translated = "Cure Wounds", + castingTime = "1 action", + range = "contact", + duration = "instantanée", + hit = null, + effect = SpellUio.Dice( + icon = R.drawable.ic_d8_24, + label = "1d8+3", + ), + changeWithLevel = true, + ), + ), + ) + ) +} diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/AttackUioFactory.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/AttackUioFactory.kt new file mode 100644 index 0000000..6808d98 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/AttackUioFactory.kt @@ -0,0 +1,84 @@ +package com.pixelized.rplexicon.ui.screens.character.factory + +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.model.Alteration +import com.pixelized.rplexicon.model.Attack +import com.pixelized.rplexicon.model.CharacterSheet +import com.pixelized.rplexicon.model.Property +import com.pixelized.rplexicon.ui.screens.character.composable.actions.AttackUio +import com.pixelized.rplexicon.utilitary.extentions.icon +import com.pixelized.rplexicon.utilitary.extentions.modifier +import javax.inject.Inject + +class AttackUioFactory @Inject constructor() { + + fun convert( + characterSheet: CharacterSheet, + alterations: Map>, + attack: Attack, + ): AttackUio { + val hit = attack.hit?.let { dice -> + // compute alteration for attack type. + val hitAlteration: Int = alterations[when (attack.type) { + Attack.Type.PHYSICAL_MELEE_ATTACK -> Property.PHYSICAL_MELEE_ATTACK + Attack.Type.PHYSICAL_RANGE_ATTACK -> Property.PHYSICAL_RANGE_ATTACK + }]?.sumOf { + it.bonus.sumOf { bonus -> bonus.value } + } ?: 0 + // compute stats modifier + val modifier = dice.modifier.sumOf { + when (it) { + Property.PROFICIENCY -> characterSheet.proficiency + Property.STRENGTH -> characterSheet.strength.modifier + Property.DEXTERITY -> characterSheet.dexterity.modifier + else -> 0 + } + } + hitAlteration + // Build the UIO. + AttackUio.Dice( + icon = dice.faces.icon, + label = "${dice.amount}d${dice.faces}${if (modifier > 0) "+$modifier" else ""}", + ) + } + + val damage = attack.damage?.let { dice -> + // compute alteration for attack type. + val damageAlteration: Int = alterations[when (attack.type) { + Attack.Type.PHYSICAL_MELEE_ATTACK -> Property.PHYSICAL_MELEE_DAMAGE + Attack.Type.PHYSICAL_RANGE_ATTACK -> Property.PHYSICAL_RANGE_DAMAGE + }]?.sumOf { + it.bonus.sumOf { bonus -> bonus.value } + } ?: 0 + // compute stats modifier + val modifier = dice.modifier.sumOf { + when (it) { + Property.PROFICIENCY -> characterSheet.proficiency + Property.STRENGTH -> characterSheet.strength.modifier + Property.DEXTERITY -> characterSheet.dexterity.modifier + else -> 0 + } + } + damageAlteration + // Build the UIO. + AttackUio.Dice( + icon = dice.faces.icon, + label = "${dice.amount}d${dice.faces}${if (modifier > 0) "+$modifier" else ""}", + ) + } + + return AttackUio( + id = attack, + icon = when (attack.type) { + Attack.Type.PHYSICAL_MELEE_ATTACK -> R.drawable.ic_crossed_swords_24 + Attack.Type.PHYSICAL_RANGE_ATTACK -> R.drawable.ic_pocket_bow_24 + }, + name = attack.title, + type = when (attack.type) { + Attack.Type.PHYSICAL_MELEE_ATTACK -> R.string.attack_type_melee + Attack.Type.PHYSICAL_RANGE_ATTACK -> R.string.attack_type_range + }, + range = attack.range, + hit = hit, + damage = damage, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetHeaderUioFactory.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetHeaderUioFactory.kt new file mode 100644 index 0000000..9c01308 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetHeaderUioFactory.kt @@ -0,0 +1,30 @@ +package com.pixelized.rplexicon.ui.screens.character.factory + +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.model.CharacterSheet +import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio +import com.pixelized.rplexicon.ui.screens.character.composable.character.LabelPointUio +import javax.inject.Inject + +class CharacterSheetHeaderUioFactory @Inject constructor() { + + fun convert(model: CharacterSheet): CharacterSheetHeaderUio { + return CharacterSheetHeaderUio( + armorClass = LabelPointUio( + label = R.string.character_sheet_title_ca, + value = model.armorClass, + max = null, + ), + speed = LabelPointUio( + label = R.string.character_sheet_title_speed, + value = "${model.speed}", + max = "m", + ), + hitPoint = LabelPointUio( + label = R.string.character_sheet_title_hp, + value = model.hitPoint, + max = "/ ${model.maxHitPoint}", + ), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/displayable/ConvertCharacterSheetIntoDisplayableFactory.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetUioFactory.kt similarity index 88% rename from app/src/main/java/com/pixelized/rplexicon/facotry/displayable/ConvertCharacterSheetIntoDisplayableFactory.kt rename to app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetUioFactory.kt index e05f72e..198055f 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/displayable/ConvertCharacterSheetIntoDisplayableFactory.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/CharacterSheetUioFactory.kt @@ -1,33 +1,19 @@ -package com.pixelized.rplexicon.facotry.displayable +package com.pixelized.rplexicon.ui.screens.character.factory import com.pixelized.rplexicon.R import com.pixelized.rplexicon.model.CharacterSheet -import com.pixelized.rplexicon.ui.screens.character.CharacterSheetUio -import com.pixelized.rplexicon.ui.screens.character.composable.LabelPointUio -import com.pixelized.rplexicon.ui.screens.character.composable.ProficiencyUio -import com.pixelized.rplexicon.ui.screens.character.composable.StatUio +import com.pixelized.rplexicon.ui.screens.character.composable.character.LabelPointUio +import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio +import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio +import com.pixelized.rplexicon.ui.screens.character.pages.proficiency.CharacterSheetUio import com.pixelized.rplexicon.utilitary.extentions.modifier import com.pixelized.rplexicon.utilitary.extentions.toLabel import javax.inject.Inject -class ConvertCharacterSheetIntoDisplayableFactory @Inject constructor() { - fun toUio(sheet: CharacterSheet): CharacterSheetUio { +class CharacterSheetUioFactory @Inject constructor() { + + fun convert(sheet: CharacterSheet): CharacterSheetUio { return CharacterSheetUio( - armorClass = LabelPointUio( - label = R.string.character_sheet_title_ca, - value = sheet.armorClass, - max = null, - ), - speed = LabelPointUio( - label = R.string.character_sheet_title_speed, - value = "${sheet.speed}", - max = "m", - ), - hitPoint = LabelPointUio( - label = R.string.character_sheet_title_hp, - value = sheet.hitPoint, - max = "/ ${sheet.maxHitPoint}", - ), initiative = LabelPointUio( label = R.string.character_sheet_title_initiative, value = sheet.dexterity.modifier.toLabel(), diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/SpellUioFactory.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/SpellUioFactory.kt new file mode 100644 index 0000000..caedd3b --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/factory/SpellUioFactory.kt @@ -0,0 +1,58 @@ +package com.pixelized.rplexicon.ui.screens.character.factory + +import com.pixelized.rplexicon.model.AssignedSpell +import com.pixelized.rplexicon.model.CharacterSheet +import com.pixelized.rplexicon.model.Property +import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellUio +import com.pixelized.rplexicon.utilitary.extentions.icon +import com.pixelized.rplexicon.utilitary.extentions.local.icon +import com.pixelized.rplexicon.utilitary.extentions.modifier +import javax.inject.Inject + +class SpellUioFactory @Inject constructor() { + + fun toUio(assignedSpell: AssignedSpell, characterSheet: CharacterSheet): SpellUio { + val hit = assignedSpell.hit?.let { dice -> + val modifier = dice.modifier.sumOf { + when (it) { + Property.PROFICIENCY -> characterSheet.proficiency + Property.INTELLIGENCE -> characterSheet.intelligence.modifier + Property.WISDOM -> characterSheet.wisdom.modifier + Property.CHARISMA -> characterSheet.charisma.modifier + else -> 0 + } + } + SpellUio.Dice( + icon = dice.faces.icon, + label = "${dice.amount}d${dice.faces}${if (modifier > 0) "+$modifier" else ""}", + ) + } + val effect = assignedSpell.effect?.let { dice -> + val modifier = dice.modifier.sumOf { + when (it) { + Property.PROFICIENCY -> characterSheet.proficiency + Property.INTELLIGENCE -> characterSheet.intelligence.modifier + Property.WISDOM -> characterSheet.wisdom.modifier + Property.CHARISMA -> characterSheet.charisma.modifier + else -> 0 + } + } + SpellUio.Dice( + icon = dice.faces.icon, + label = "${dice.amount}d${dice.faces}${if (modifier > 0) "+$modifier" else ""}", + ) + } + return SpellUio( + icon = assignedSpell.spell.school.icon, + name = assignedSpell.spell.name, + translated = assignedSpell.spell.originalName, + castingTime = assignedSpell.spell.castingTime, + range = assignedSpell.spell.range, + duration = assignedSpell.spell.duration, + hit = hit, + effect = effect, + changeWithLevel = assignedSpell.level != null, + ) + } +} + diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/ActionsPage.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/ActionsPage.kt deleted file mode 100644 index e7e9fef..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/ActionsPage.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.pixelized.rplexicon.ui.screens.character.pages - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.pixelized.rplexicon.ui.screens.character.composable.ActionsUio -import com.pixelized.rplexicon.ui.screens.character.composable.CounterUio -import com.pixelized.rplexicon.ui.screens.character.composable.Weapon -import com.pixelized.rplexicon.ui.screens.character.composable.WeaponUio - -@Composable -fun ActionsPages( - modifier: Modifier = Modifier, - lazyListState: LazyListState = rememberLazyListState(), - weapons: State>, - onHit: (id: String) -> Unit, - onDamage: (id: String) -> Unit, -) { - LazyColumn( - modifier = modifier, - state = lazyListState, - verticalArrangement = Arrangement.spacedBy(space = 16.dp), - ) { - items(items = weapons.value) { - Weapon( - weapon = it, - onHit = onHit, - onDamage = onDamage, - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ActionsPage.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ActionsPage.kt new file mode 100644 index 0000000..7033fad --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ActionsPage.kt @@ -0,0 +1,165 @@ +package com.pixelized.rplexicon.ui.screens.character.pages.actions + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SheetState +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.pixelized.rplexicon.LocalRollOverlay +import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost +import com.pixelized.rplexicon.ui.navigation.screens.navigateToSpellDetail +import com.pixelized.rplexicon.ui.screens.character.composable.actions.Attack +import com.pixelized.rplexicon.ui.screens.character.composable.actions.AttackUio +import com.pixelized.rplexicon.ui.screens.character.composable.actions.Spell +import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellHeader +import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellHeaderUio +import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellUio +import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberAttackListStatePreview +import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberSpellListStatePreview +import com.pixelized.rplexicon.ui.theme.LexiconTheme +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun ActionPage( + character: String, + sheetState: ModalBottomSheetState, + attackViewModel: AttackActionViewModel = hiltViewModel(), + spellsViewModel: SpellsActionViewModel = hiltViewModel(), +) { + val screen = LocalScreenNavHost.current + val overlay = LocalRollOverlay.current + val scope = rememberCoroutineScope() + + ActionsPageContent( + modifier = Modifier.fillMaxWidth(), + attacks = attackViewModel.attacks, + spells = spellsViewModel.spells, + onAttackHit = { id -> + val roll = attackViewModel.onHitRoll(id) + overlay.prepareRoll(roll = roll) + overlay.showOverlay() + }, + onAttackDamage = { id -> + val roll = attackViewModel.onDamageRoll(id) + overlay.prepareRoll(roll = roll) + overlay.showOverlay() + }, + onSpell = { spell -> + screen.navigateToSpellDetail( + character = character, + spell = spell, + ) + }, + onSpellHit = { id -> + val roll = spellsViewModel.onSpellHitRoll(id) + overlay.prepareRoll(roll = roll) + overlay.showOverlay() + }, + onSpellDamage = { id -> + val roll = spellsViewModel.onSpellDamageRoll(id) + overlay.prepareRoll(roll = roll) + overlay.showOverlay() + }, + onCast = { + if (spellsViewModel.shouldDisplaySpellLevelChooser(it)) { + spellsViewModel.prepareSpellCast(it) + scope.launch { sheetState.show() } + } else { + val roll = spellsViewModel.onCastSpell(it) + overlay.prepareRoll(roll = roll) + overlay.showOverlay() + } + }, + ) +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun ActionsPageContent( + modifier: Modifier = Modifier, + lazyListState: LazyListState = rememberLazyListState(), + attacks: State>, + spells: State>>>, + onAttackHit: (id: String) -> Unit, + onAttackDamage: (id: String) -> Unit, + onSpell: (id: String) -> Unit, + onSpellHit: (id: String) -> Unit, + onSpellDamage: (id: String) -> Unit, + onCast: (id: String) -> Unit, +) { + LazyColumn( + modifier = modifier, + state = lazyListState, + contentPadding = PaddingValues(vertical = 16.dp), + verticalArrangement = Arrangement.spacedBy(space = 16.dp), + ) { + items(items = attacks.value) { + Attack( + modifier = Modifier.padding(horizontal = 16.dp), + weapon = it, + onHit = onAttackHit, + onDamage = onAttackDamage, + ) + } + + spells.value.forEach { entry -> + stickyHeader { + SpellHeader( + header = entry.first, + ) + } + + items(items = entry.second) { + Spell( + modifier = Modifier.padding(horizontal = 16.dp), + spell = it, + onClick = onSpell, + onHit = onSpellHit, + onEffect = onSpellDamage, + onCast = onCast, + ) + } + } + } +} + +@Composable +@Preview(uiMode = UI_MODE_NIGHT_NO) +@Preview(uiMode = UI_MODE_NIGHT_YES) +fun ActionPagePreview() { + LexiconTheme { + Surface { + ActionsPageContent( + modifier = Modifier.fillMaxSize(), + attacks = rememberAttackListStatePreview(), + spells = rememberSpellListStatePreview(), + onAttackHit = { }, + onAttackDamage = { }, + onSpell = { }, + onSpellHit = { }, + onSpellDamage = { }, + onCast = { }, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/AttackActionViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/AttackActionViewModel.kt new file mode 100644 index 0000000..43dff94 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/AttackActionViewModel.kt @@ -0,0 +1,224 @@ +package com.pixelized.rplexicon.ui.screens.character.pages.actions + +import android.app.Application +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.model.Attack +import com.pixelized.rplexicon.model.CharacterSheet +import com.pixelized.rplexicon.model.Property +import com.pixelized.rplexicon.model.Roll +import com.pixelized.rplexicon.model.Throw +import com.pixelized.rplexicon.repository.data.ActionRepository +import com.pixelized.rplexicon.repository.data.AlterationRepository +import com.pixelized.rplexicon.repository.data.CharacterSheetRepository +import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument +import com.pixelized.rplexicon.ui.screens.character.composable.actions.AttackUio +import com.pixelized.rplexicon.ui.screens.character.factory.AttackUioFactory +import com.pixelized.rplexicon.utilitary.extentions.context +import com.pixelized.rplexicon.utilitary.extentions.local.status +import com.pixelized.rplexicon.utilitary.extentions.modifier +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject + +@HiltViewModel +class AttackActionViewModel @Inject constructor( + application: Application, + savedStateHandle: SavedStateHandle, + private val characterRepository: CharacterSheetRepository, + private val alterationRepository: AlterationRepository, + private val actionRepository: ActionRepository, + attackFactory: AttackUioFactory, +) : AndroidViewModel(application) { + private val character = savedStateHandle.characterSheetArgument.name + private val model: CharacterSheet get() = characterRepository.data.value.getValue(character) + + private val _attacks = mutableStateOf>(emptyList()) + val attacks: State> get() = _attacks + + init { + viewModelScope.launch { + launch { + actionRepository.data.collect { sheets -> + _attacks.value = withContext(Dispatchers.Default) { + val alterations = alterationRepository.getAlterations(character = character) + sheets[character]?.map { action -> + attackFactory.convert( + characterSheet = model, + alterations = alterations.status(), + attack = action, + ) + } ?: emptyList() + } + } + } + } + } + + fun onHitRoll(id: String): Roll { + val action = actionRepository.find(character)?.firstOrNull { it.title == id } + return actionRoll( + attack = action, + throws = action?.hit, + ) + } + + fun onDamageRoll(id: String): Roll { + val action = actionRepository.find(character)?.firstOrNull { it.title == id } + return actionRoll( + attack = action, + throws = action?.damage, + ) + } + + private fun actionRoll( + attack: Attack?, + throws: Throw?, + ): Roll { + // build the title + val title = context.getString( + when (attack?.type) { + else -> when (throws === attack?.hit) { + true -> R.string.dice_roll_attack_hit_title + else -> R.string.dice_roll_attack_damage_title + } + }, + attack?.title, + ) + // get the alteration for roll and a given player + val alterations = when (attack?.type) { + Attack.Type.PHYSICAL_MELEE_ATTACK -> { + alterationRepository.getStatus( + character = model.name, + property = when (throws === attack.hit) { + true -> Property.PHYSICAL_MELEE_ATTACK + else -> Property.PHYSICAL_MELEE_DAMAGE + }, + ) + } + + Attack.Type.PHYSICAL_RANGE_ATTACK -> { + alterationRepository.getStatus( + character = model.name, + property = when (throws === attack.hit) { + true -> Property.PHYSICAL_RANGE_ATTACK + else -> Property.PHYSICAL_RANGE_DAMAGE + }, + ) + } + + else -> { + emptyList() + } + } + // check if any alteration give us advantage or disadvantage + val advantage = alterations.any { it.advantage } + val disadvantage = alterations.any { it.disadvantage } + val fail = alterations.any { it.fail } + // build the roll + return Roll( + title = title, + highlight = attack?.title, + dices = listOf( + Roll.Dice( + title = attack?.title, + advantage = advantage, + disadvantage = disadvantage, + fail = fail, + count = throws?.amount ?: 1, + faces = throws?.faces ?: 20, + ) + ) + alterations.map { it.dices }.flatten(), + bonus = (throws?.modifier + ?.mapNotNull { + when (it) { + Property.PROFICIENCY -> { + Roll.Bonus( + title = context.getString(R.string.dice_roll_proficiency_bonus), + value = model.proficiency, + ) + } + + Property.STRENGTH -> { + val related = + context.getString(R.string.character_sheet_stat_strength) + Roll.Bonus( + title = context.getString( + R.string.dice_roll_bonus_detail, + related + ), + value = model.strength.modifier, + ) + } + + Property.DEXTERITY -> { + val related = + context.getString(R.string.character_sheet_stat_dexterity) + Roll.Bonus( + title = context.getString( + R.string.dice_roll_bonus_detail, + related + ), + value = model.dexterity.modifier, + ) + } + + Property.CONSTITUTION -> { + val related = + context.getString(R.string.character_sheet_stat_constitution) + Roll.Bonus( + title = context.getString( + R.string.dice_roll_bonus_detail, + related + ), + value = model.constitution.modifier, + ) + } + + Property.INTELLIGENCE -> { + val related = + context.getString(R.string.character_sheet_stat_intelligence) + Roll.Bonus( + title = context.getString( + R.string.dice_roll_bonus_detail, + related + ), + value = model.intelligence.modifier, + ) + } + + Property.WISDOM -> { + val related = + context.getString(R.string.character_sheet_stat_wisdom) + Roll.Bonus( + title = context.getString( + R.string.dice_roll_bonus_detail, + related + ), + value = model.wisdom.modifier, + ) + } + + Property.CHARISMA -> { + val related = context.getString(R.string.character_sheet_stat_charisma) + Roll.Bonus( + title = context.getString( + R.string.dice_roll_bonus_detail, + related + ), + value = model.charisma.modifier, + ) + } + + else -> null + } + } ?: emptyList()) + alterations.map { it.bonus }.flatten(), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SpellsActionViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SpellsActionViewModel.kt new file mode 100644 index 0000000..c1fc593 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/SpellsActionViewModel.kt @@ -0,0 +1,304 @@ +package com.pixelized.rplexicon.ui.screens.character.pages.actions + +import android.app.Application +import androidx.annotation.StringRes +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.model.CharacterSheet +import com.pixelized.rplexicon.model.Property +import com.pixelized.rplexicon.model.Roll +import com.pixelized.rplexicon.model.Spell +import com.pixelized.rplexicon.model.Throw +import com.pixelized.rplexicon.repository.data.CharacterSheetRepository +import com.pixelized.rplexicon.repository.data.SpellRepository +import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument +import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellHeaderUio +import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellLevelUio +import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellUio +import com.pixelized.rplexicon.ui.screens.character.factory.SpellUioFactory +import com.pixelized.rplexicon.ui.screens.character.pages.chooser.SpellChooserUio +import com.pixelized.rplexicon.utilitary.extentions.context +import com.pixelized.rplexicon.utilitary.extentions.icon +import com.pixelized.rplexicon.utilitary.extentions.local.firstSpellSlot +import com.pixelized.rplexicon.utilitary.extentions.local.highestSpellLevel +import com.pixelized.rplexicon.utilitary.extentions.local.spell +import com.pixelized.rplexicon.utilitary.extentions.local.toString +import com.pixelized.rplexicon.utilitary.extentions.modifier +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import kotlin.math.max + +@HiltViewModel +class SpellsActionViewModel @Inject constructor( + application: Application, + savedStateHandle: SavedStateHandle, + private val characterRepository: CharacterSheetRepository, + private val spellRepository: SpellRepository, + spellFactory: SpellUioFactory, +) : AndroidViewModel(application) { + private val character = savedStateHandle.characterSheetArgument.name + private val model: CharacterSheet get() = characterRepository.data.value.getValue(character) + + private val _spells = mutableStateOf>>>(emptyList()) + val spells: State>>> get() = _spells + + private val _preparedSpellLevel = mutableStateOf(null) + val preparedSpellLevel: State get() = _preparedSpellLevel + + init { + viewModelScope.launch { + launch { + spellRepository.spells.collect { + _spells.value = withContext(Dispatchers.Default) { + if (model.isWarlock) { + it[character] + ?.sortedBy { it.spell.name } + ?.sortedBy { it.spell.level } + ?.groupBy { it.spell.level == 0 } + ?.map { entry -> + if (entry.key) { + SpellHeaderUio( + level = 0, + count = null, + ) + } else { + val firstSpellSlot = model.firstSpellSlot() + SpellHeaderUio( + level = firstSpellSlot ?: 1, + count = model.spell(level = firstSpellSlot ?: 1)?.let { + SpellHeaderUio.Count( + it.value, + it.max + ) + } + ) + } to entry.value.map { + spellFactory.toUio( + assignedSpell = it, + characterSheet = model + ) + } + } + ?: emptyList() + } else { + it[character] + ?.sortedBy { it.spell.name } + ?.sortedBy { it.spell.level } + ?.groupBy { it.spell.level } + ?.map { entry -> + SpellHeaderUio( + level = entry.key, + count = model.spell(level = entry.key)?.let { + SpellHeaderUio.Count( + it.value, + it.max + ) + }, + ) to entry.value.map { + spellFactory.toUio( + assignedSpell = it, + characterSheet = model + ) + } + } + ?: emptyList() + } + } + } + } + } + } + + fun shouldDisplaySpellLevelChooser(name: String): Boolean { + val assignedSpell = spellRepository.findAssignedSpell( + character = character, + spell = name, + ) + return when (model.isWarlock) { + true -> false + else -> model.highestSpellLevel() > (assignedSpell?.spell?.level ?: 1) + } + } + + fun prepareSpellCast(name: String) { + val assignedSpell = spellRepository.findAssignedSpell( + character = character, + spell = name, + ) + if (assignedSpell != null) { + val icon = assignedSpell.effect?.faces?.icon ?: R.drawable.ic_d20_24 + val base = assignedSpell.effect?.toString(character = model, level = 1) ?: "" + _preparedSpellLevel.value = SpellChooserUio( + name = name, + spells = List( + size = max(0, model.highestSpellLevel() + 1 - assignedSpell.spell.level) + ) { index -> + val level = assignedSpell.spell.level + index + val remaining = model.spell(level)?.value + val max = model.spell(level)?.max + + SpellLevelUio( + spell = assignedSpell.spell.name, + level = level, + remaining = remaining, + max = max, + icon = icon, + value = base + (assignedSpell.level + ?.toString(character = model, level = index) + ?.let { " + $it" } + ?: "") + ) + }, + ) + } + } + + fun onCastSpell(id: String): Roll { + val spell = spellRepository.findAssignedSpell( + character = character, + spell = id, + ) + return onCastSpell( + id = id, + level = when (model.isWarlock) { + true -> model.firstSpellSlot() ?: 1 + else -> spell?.spell?.level ?: 1 + }, + ) + } + + fun onCastSpell(id: String, level: Int): Roll { + val spell = spellRepository.findAssignedSpell( + character = character, + spell = id, + ) + return spellRoll( + spell = spell?.spell, + titleRes = R.string.dice_roll_spell_cast, + throws = spell?.effect, + level = level, + levelThrow = spell?.level, + ) + } + + fun onSpellHitRoll(id: String): Roll { + val spell = spellRepository.findAssignedSpell( + character = character, + spell = id, + ) + return spellRoll( + spell = spell?.spell, + titleRes = R.string.dice_roll_attack_hit_title, + throws = spell?.hit, + ) + } + + fun onSpellDamageRoll(id: String): Roll { + val spell = spellRepository.findAssignedSpell( + character = character, + spell = id, + ) + return spellRoll( + spell = spell?.spell, + titleRes = R.string.dice_roll_attack_damage_title, + throws = spell?.effect, + ) + } + + //////////////////////////////////////// + // region: Helpers + + private fun spellRoll( + spell: Spell?, + @StringRes titleRes: Int, + throws: Throw?, + level: Int = 0, + levelThrow: Throw? = null, + ): Roll { + // build the title + val title = context.getString(titleRes, spell?.name) + // build the roll + return Roll( + title = title, + highlight = spell?.name, + dices = listOf( + Roll.Dice( + title = spell?.name, + advantage = false, + disadvantage = false, + fail = false, + count = throws?.amount ?: 1, + faces = throws?.faces ?: 20, + ) + ) + List(max(0, level - (spell?.level ?: 0))) { + Roll.Dice( + title = context.getString( + R.string.spell_level_chooser_label, + ((spell?.level ?: 0) + it + 1).toString() + ), + advantage = false, + disadvantage = false, + fail = false, + count = levelThrow?.amount ?: 1, + faces = levelThrow?.faces ?: 20, + ) + }, + bonus = (throws?.modifier + ?.mapNotNull { + when (it) { + Property.PROFICIENCY -> { + Roll.Bonus( + title = context.getString(R.string.dice_roll_proficiency_bonus), + value = model.proficiency, + ) + } + + Property.INTELLIGENCE -> { + val related = + context.getString(R.string.character_sheet_stat_intelligence) + Roll.Bonus( + title = context.getString( + R.string.dice_roll_bonus_detail, + related + ), + value = model.intelligence.modifier, + ) + } + + Property.WISDOM -> { + val related = + context.getString(R.string.character_sheet_stat_wisdom) + Roll.Bonus( + title = context.getString( + R.string.dice_roll_bonus_detail, + related + ), + value = model.wisdom.modifier, + ) + } + + Property.CHARISMA -> { + val related = + context.getString(R.string.character_sheet_stat_charisma) + Roll.Bonus( + title = context.getString( + R.string.dice_roll_bonus_detail, + related + ), + value = model.charisma.modifier, + ) + } + + else -> null + } + } ?: emptyList()), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/chooser/SpellLevelChooser.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/chooser/SpellLevelChooser.kt new file mode 100644 index 0000000..df05671 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/chooser/SpellLevelChooser.kt @@ -0,0 +1,75 @@ +package com.pixelized.rplexicon.ui.screens.character.pages.chooser + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.Divider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellLevelItem +import com.pixelized.rplexicon.ui.screens.character.composable.actions.SpellLevelUio +import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberSpellLevelChooserState +import com.pixelized.rplexicon.ui.theme.LexiconTheme + +@Stable +data class SpellChooserUio( + val name: String, + val spells: List, +) + +@Composable +fun SpellLevelChooser( + modifier: Modifier = Modifier, + spells: State, + onLevel: (spell: String, level: Int) -> Unit, +) { + LazyColumn( + modifier = modifier, + ) { + item { + Text( + modifier = Modifier + .padding(top = 16.dp) + .padding(horizontal = 16.dp), + style = MaterialTheme.typography.titleLarge, + text = spells.value?.name ?: "" + ) + } + itemsIndexed( + items = spells.value?.spells ?: emptyList(), + ) { index, item -> + SpellLevelItem( + spell = item, + onSpellCast = onLevel, + ) + if (index < (spells.value?.spells?.lastIndex ?: 0)) { + Divider(modifier = Modifier.padding(horizontal = 16.dp)) + } + } + } +} + +@Composable +@Preview(uiMode = UI_MODE_NIGHT_NO) +@Preview(uiMode = UI_MODE_NIGHT_YES) +fun SpellLevelChooserPreview() { + LexiconTheme { + Surface { + SpellLevelChooser( + spells = rememberSpellLevelChooserState(), + onLevel = { _, _ -> }, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/ProficiencyPage.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyPage.kt similarity index 62% rename from app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/ProficiencyPage.kt rename to app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyPage.kt index d1ff5a9..b91c5c2 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/ProficiencyPage.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyPage.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.ui.screens.character.pages +package com.pixelized.rplexicon.ui.screens.character.pages.proficiency import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.rememberScrollState @@ -18,6 +19,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -30,18 +33,89 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.pixelized.rplexicon.LocalRollOverlay import com.pixelized.rplexicon.R -import com.pixelized.rplexicon.ui.screens.character.CharacterSheetUio -import com.pixelized.rplexicon.ui.screens.character.composable.Proficiency -import com.pixelized.rplexicon.ui.screens.character.composable.ProficiencyUio -import com.pixelized.rplexicon.ui.screens.character.composable.Stat -import com.pixelized.rplexicon.ui.screens.character.composable.StatUio -import com.pixelized.rplexicon.ui.screens.character.preview.rememberCharacterSheetPreview +import com.pixelized.rplexicon.ui.screens.character.composable.character.LabelPointUio +import com.pixelized.rplexicon.ui.screens.character.composable.character.Proficiency +import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio +import com.pixelized.rplexicon.ui.screens.character.composable.character.Stat +import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio +import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberCharacterSheetStatePreview import com.pixelized.rplexicon.ui.theme.LexiconTheme import com.pixelized.rplexicon.utilitary.extentions.ddBorder +@Stable +data class CharacterSheetUio( + val initiative: LabelPointUio, + val stats: List, + val savingThrows: List, + val proficiencies: List, +) + @Composable fun ProficiencyPage( + viewModel: ProficiencyViewModel = hiltViewModel(), +) { + val overlay = LocalRollOverlay.current + + viewModel.sheet.value?.let { sheet -> + ProficiencyPageContent( + modifier = Modifier.fillMaxWidth(), + sheet = sheet, + onInitiative = { + val roll = viewModel.initiativeRoll() + overlay.prepareRoll(roll = roll) + overlay.showOverlay() + }, + onStats = { stat -> + val roll = when (stat.id) { + StatUio.ID.STRENGTH -> viewModel.strengthRoll() + StatUio.ID.DEXTERITY -> viewModel.dexterityRoll() + StatUio.ID.CONSTITUTION -> viewModel.constitutionRoll() + StatUio.ID.INTELLIGENCE -> viewModel.intelligenceRoll() + StatUio.ID.WISDOM -> viewModel.wisdomRoll() + StatUio.ID.CHARISMA -> viewModel.charismaRoll() + } + overlay.prepareRoll(roll = roll) + overlay.showOverlay() + }, + onProficiencies = { proficiency -> + val roll = when (proficiency.id) { + ProficiencyUio.ID.STRENGTH_SAVING_THROW -> viewModel.strengthSavingThrowsRoll() + ProficiencyUio.ID.DEXTERITY_SAVING_THROW -> viewModel.dexteritySavingThrowsRoll() + ProficiencyUio.ID.CONSTITUTION_SAVING_THROW -> viewModel.constitutionSavingThrowsRoll() + ProficiencyUio.ID.INTELLIGENCE_SAVING_THROW -> viewModel.intelligenceSavingThrowsRoll() + ProficiencyUio.ID.WISDOM_SAVING_THROW -> viewModel.wisdomSavingThrowsRoll() + ProficiencyUio.ID.CHARISMA_SAVING_THROW -> viewModel.charismaSavingThrowsRoll() + ProficiencyUio.ID.ACROBATICS -> viewModel.acrobaticsRoll() + ProficiencyUio.ID.ANIMAL_HANDLING -> viewModel.animalHandlingRoll() + ProficiencyUio.ID.ARCANA -> viewModel.arcanaRoll() + ProficiencyUio.ID.ATHLETICS -> viewModel.athleticsRoll() + ProficiencyUio.ID.DECEPTION -> viewModel.deceptionRoll() + ProficiencyUio.ID.HISTORY -> viewModel.historyRoll() + ProficiencyUio.ID.INSIGHT -> viewModel.insightRoll() + ProficiencyUio.ID.INTIMIDATION -> viewModel.intimidationRoll() + ProficiencyUio.ID.INVESTIGATION -> viewModel.investigationRoll() + ProficiencyUio.ID.MEDICINE -> viewModel.medicineRoll() + ProficiencyUio.ID.NATURE -> viewModel.natureRoll() + ProficiencyUio.ID.PERCEPTION -> viewModel.perceptionRoll() + ProficiencyUio.ID.PERFORMANCE -> viewModel.performanceRoll() + ProficiencyUio.ID.PERSUASION -> viewModel.persuasionRoll() + ProficiencyUio.ID.RELIGION -> viewModel.religionRoll() + ProficiencyUio.ID.SLEIGHT_OF_HAND -> viewModel.sleightOfHandRoll() + ProficiencyUio.ID.STEALTH -> viewModel.stealthRoll() + ProficiencyUio.ID.SURVIVAL -> viewModel.survivalRoll() + } + overlay.prepareRoll(roll = roll) + overlay.showOverlay() + }, + ) + } +} + +@Composable +fun ProficiencyPageContent( modifier: Modifier = Modifier, state: ScrollState = rememberScrollState(), inner: Shape = remember { RoundedCornerShape(size = 8.dp) }, @@ -51,7 +125,11 @@ fun ProficiencyPage( onStats: (StatUio) -> Unit, onProficiencies: (ProficiencyUio) -> Unit, ) { - Box(modifier = modifier.verticalScroll(state = state)) { + Box( + modifier = modifier + .verticalScroll(state = state) + .padding(all = 16.dp), + ) { ProficiencyLayout( inner = inner, outline = outline, @@ -198,12 +276,12 @@ val List.proficiencies get() = first { it.layoutId == "Proficiencies @Composable @Preview(uiMode = UI_MODE_NIGHT_NO, heightDp = 1060) @Preview(uiMode = UI_MODE_NIGHT_YES, heightDp = 1060) -private fun ProficiencyPreview() { +fun ProficiencyPreview() { LexiconTheme { Surface { - ProficiencyPage( - modifier = Modifier.padding(all = 16.dp), - sheet = rememberCharacterSheetPreview(), + val sheet by rememberCharacterSheetStatePreview() + ProficiencyPageContent( + sheet = sheet, onInitiative = { }, onStats = { }, onProficiencies = { }, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyViewModel.kt new file mode 100644 index 0000000..bc0546a --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/proficiency/ProficiencyViewModel.kt @@ -0,0 +1,419 @@ +package com.pixelized.rplexicon.ui.screens.character.pages.proficiency + +import android.app.Application +import androidx.annotation.StringRes +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.model.CharacterSheet +import com.pixelized.rplexicon.model.Property +import com.pixelized.rplexicon.model.Roll +import com.pixelized.rplexicon.model.d20 +import com.pixelized.rplexicon.repository.data.AlterationRepository +import com.pixelized.rplexicon.repository.data.CharacterSheetRepository +import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument +import com.pixelized.rplexicon.ui.screens.character.factory.CharacterSheetUioFactory +import com.pixelized.rplexicon.utilitary.extentions.context +import com.pixelized.rplexicon.utilitary.extentions.modifier +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject + +@HiltViewModel +class ProficiencyViewModel @Inject constructor( + application: Application, + savedStateHandle: SavedStateHandle, + private val characterRepository: CharacterSheetRepository, + private val alterationRepository: AlterationRepository, + private val characterSheetFactory: CharacterSheetUioFactory, +) : AndroidViewModel(application) { + private val character = savedStateHandle.characterSheetArgument.name + private var model: CharacterSheet = characterRepository.data.value.getValue(character) + + private val _sheet = mutableStateOf(null) + val sheet: State get() = _sheet + + init { + viewModelScope.launch { + launch { + characterRepository.data.collect { + _sheet.value = withContext(Dispatchers.Default) { + model = it.getValue(key = character) + characterSheetFactory.convert(sheet = model) + } + } + } + } + } + + fun initiativeRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_title_initiative, + relatedRes = R.string.character_sheet_stat_dexterity, + masteryLevel = null, + abilityValue = model.dexterity, + property = Property.INITIATIVE, + ) + + fun strengthRoll(): Roll = statRoll( + abilityRes = R.string.character_sheet_stat_strength, + abilityValue = model.strength, + property = Property.STRENGTH, + ) + + fun dexterityRoll(): Roll = statRoll( + abilityRes = R.string.character_sheet_stat_dexterity, + abilityValue = model.dexterity, + property = Property.DEXTERITY, + ) + + fun constitutionRoll(): Roll = statRoll( + abilityRes = R.string.character_sheet_stat_constitution, + abilityValue = model.constitution, + property = Property.CONSTITUTION, + ) + + fun intelligenceRoll(): Roll = statRoll( + abilityRes = R.string.character_sheet_stat_intelligence, + abilityValue = model.intelligence, + property = Property.INTELLIGENCE, + ) + + fun wisdomRoll(): Roll = statRoll( + abilityRes = R.string.character_sheet_stat_wisdom, + abilityValue = model.wisdom, + property = Property.WISDOM, + ) + + fun charismaRoll(): Roll = statRoll( + abilityRes = R.string.character_sheet_stat_charisma, + abilityValue = model.charisma, + property = Property.CHARISMA, + ) + + fun strengthSavingThrowsRoll(): Roll = savingThrowRoll( + abilityRes = R.string.character_sheet_stat_strength, + abilityValue = model.strength, + masteryLevel = model.strengthSavingThrows, + property = Property.STRENGTH_SAVING_THROW, + ) + + fun dexteritySavingThrowsRoll(): Roll = savingThrowRoll( + abilityRes = R.string.character_sheet_stat_dexterity, + abilityValue = model.dexterity, + masteryLevel = model.dexteritySavingThrows, + property = Property.DEXTERITY_SAVING_THROW, + ) + + fun constitutionSavingThrowsRoll(): Roll = savingThrowRoll( + abilityRes = R.string.character_sheet_stat_constitution, + abilityValue = model.constitution, + masteryLevel = model.constitutionSavingThrows, + property = Property.CONSTITUTION_SAVING_THROW, + ) + + fun intelligenceSavingThrowsRoll(): Roll = savingThrowRoll( + abilityRes = R.string.character_sheet_stat_intelligence, + abilityValue = model.intelligence, + masteryLevel = model.intelligenceSavingThrows, + property = Property.INTELLIGENCE_SAVING_THROW, + ) + + fun wisdomSavingThrowsRoll(): Roll = savingThrowRoll( + abilityRes = R.string.character_sheet_stat_wisdom, + abilityValue = model.wisdom, + masteryLevel = model.wisdomSavingThrows, + property = Property.WISDOM_SAVING_THROW, + ) + + fun charismaSavingThrowsRoll(): Roll = savingThrowRoll( + abilityRes = R.string.character_sheet_stat_charisma, + abilityValue = model.charisma, + masteryLevel = model.charismaSavingThrows, + property = Property.CHARISMA_SAVING_THROW, + ) + + fun acrobaticsRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_acrobatics, + relatedRes = R.string.character_sheet_stat_dexterity, + abilityValue = model.dexterity, + masteryLevel = model.acrobatics, + property = Property.ACROBATICS, + ) + + fun animalHandlingRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_animal_handling, + relatedRes = R.string.character_sheet_stat_wisdom, + abilityValue = model.wisdom, + masteryLevel = model.animalHandling, + property = Property.ANIMAL_HANDLING, + ) + + fun arcanaRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_arcana, + relatedRes = R.string.character_sheet_stat_intelligence, + abilityValue = model.intelligence, + masteryLevel = model.arcana, + property = Property.ARCANA, + ) + + fun athleticsRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_athletics, + relatedRes = R.string.character_sheet_stat_strength, + abilityValue = model.strength, + masteryLevel = model.athletics, + property = Property.ATHLETICS, + ) + + fun deceptionRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_deception, + relatedRes = R.string.character_sheet_stat_charisma, + abilityValue = model.charisma, + masteryLevel = model.deception, + property = Property.DECEPTION, + ) + + fun historyRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_history, + relatedRes = R.string.character_sheet_stat_intelligence, + abilityValue = model.intelligence, + masteryLevel = model.history, + property = Property.HISTORY, + ) + + fun insightRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_insight, + relatedRes = R.string.character_sheet_stat_wisdom, + abilityValue = model.wisdom, + masteryLevel = model.insight, + property = Property.INSIGHT, + ) + + fun intimidationRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_intimidation, + relatedRes = R.string.character_sheet_stat_charisma, + abilityValue = model.charisma, + masteryLevel = model.intimidation, + property = Property.INTIMIDATION, + ) + + fun investigationRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_investigation, + relatedRes = R.string.character_sheet_stat_intelligence, + abilityValue = model.intelligence, + masteryLevel = model.investigation, + property = Property.INVESTIGATION, + ) + + fun medicineRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_medicine, + relatedRes = R.string.character_sheet_stat_wisdom, + abilityValue = model.wisdom, + masteryLevel = model.medicine, + property = Property.MEDICINE, + ) + + fun natureRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_nature, + relatedRes = R.string.character_sheet_stat_intelligence, + abilityValue = model.intelligence, + masteryLevel = model.nature, + property = Property.NATURE, + ) + + fun perceptionRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_perception, + relatedRes = R.string.character_sheet_stat_wisdom, + abilityValue = model.wisdom, + masteryLevel = model.perception, + property = Property.PERCEPTION, + ) + + fun performanceRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_performance, + relatedRes = R.string.character_sheet_stat_charisma, + abilityValue = model.charisma, + masteryLevel = model.performance, + property = Property.PERFORMANCE, + ) + + fun persuasionRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_persuasion, + relatedRes = R.string.character_sheet_stat_charisma, + abilityValue = model.charisma, + masteryLevel = model.persuasion, + property = Property.PERSUASION, + ) + + fun religionRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_religion, + relatedRes = R.string.character_sheet_stat_intelligence, + abilityValue = model.intelligence, + masteryLevel = model.religion, + property = Property.RELIGION, + ) + + fun sleightOfHandRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_sleight_of_hand, + relatedRes = R.string.character_sheet_stat_dexterity, + abilityValue = model.dexterity, + masteryLevel = model.sleightOfHand, + property = Property.SLEIGHT_OF_HAND, + ) + + fun stealthRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_stealth, + relatedRes = R.string.character_sheet_stat_dexterity, + abilityValue = model.dexterity, + masteryLevel = model.stealth, + property = Property.STEALTH, + ) + + fun survivalRoll(): Roll = abilityRoll( + abilityRes = R.string.character_sheet_proficiency_survival, + relatedRes = R.string.character_sheet_stat_wisdom, + abilityValue = model.wisdom, + masteryLevel = model.survival, + property = Property.SURVIVAL, + ) + + private fun statRoll( + abilityRes: Int, + abilityValue: Int, + property: Property, + ): Roll { + val ability = context.getString(abilityRes) + // get the alteration for a given player + val alterations = alterationRepository.getStatus( + character = model.name, + property = property, + ) + // check if any alteration give us advantage or disadvantage + val advantage = alterations.any { it.advantage } + val disadvantage = alterations.any { it.disadvantage } + val fail = alterations.any { it.fail } + + return Roll( + title = context.getString(R.string.dice_roll_check_title, ability.uppercase()), + highlight = ability, + dices = listOf( + d20( + title = context.getString(R.string.dice_roll_check_detail, ability), + advantage = advantage, + disadvantage = disadvantage, + fail = fail, + ), + ) + alterations.map { it.dices }.flatten(), + bonus = listOf( + Roll.Bonus( + title = context.getString(R.string.dice_roll_bonus_detail, ability), + value = abilityValue.modifier + ) + ) + alterations.map { it.bonus }.flatten(), + ) + } + + private fun savingThrowRoll( + @StringRes abilityRes: Int, + abilityValue: Int, + masteryLevel: Int, + masteryValue: Int = model.proficiency, + property: Property, + ): Roll { + val ability = context.getString(abilityRes) + + // get the alteration for a given player + val alterations = alterationRepository.getStatus( + character = model.name, + property = property, + ) + // check if any alteration give us advantage or disadvantage + val advantage = alterations.any { it.advantage } + val disadvantage = alterations.any { it.disadvantage } + val fail = alterations.any { it.fail } + + return Roll( + title = context.getString( + R.string.dice_roll_saving_throw_title, + ability.uppercase() + ), + highlight = ability, + dices = listOf( + d20( + title = context.getString(R.string.dice_roll_saving_throw_detail, ability), + advantage = advantage, + disadvantage = disadvantage, + fail = fail, + ), + ) + alterations.map { it.dices }.flatten(), + bonus = listOf( + Roll.Bonus( + title = context.getString( + if (masteryLevel == 2) R.string.dice_roll_mastery_expertise else R.string.dice_roll_mastery_proficiency, + context.getString(R.string.dice_roll_mastery_saving_throw) + ), + value = masteryValue * masteryLevel, + ), + Roll.Bonus( + title = context.getString(R.string.dice_roll_bonus_detail, ability), + value = abilityValue.modifier + ), + ) + alterations.map { it.bonus }.flatten(), + ) + } + + private fun abilityRoll( + @StringRes abilityRes: Int, + @StringRes relatedRes: Int, + abilityValue: Int, + masteryLevel: Int?, + masteryValue: Int = model.proficiency, + property: Property, + ): Roll { + val ability = context.getString(abilityRes) + val related = context.getString(relatedRes) + + // get the alteration for a given player + val alterations = alterationRepository.getStatus( + character = model.name, + property = property, + ) + // check if any alteration give us advantage or disadvantage + val advantage = alterations.any { it.advantage } + val disadvantage = alterations.any { it.disadvantage } + val fail = alterations.any { it.fail } + + return Roll( + title = context.getString(R.string.dice_roll_check_title, ability.uppercase()), + highlight = ability, + dices = listOf( + d20( + title = context.getString(R.string.dice_roll_check_detail, ability), + advantage = advantage, + disadvantage = disadvantage, + fail = fail, + ), + ) + alterations.map { it.dices }.flatten(), + bonus = (masteryLevel?.let { + listOf( + Roll.Bonus( + title = context.getString( + if (masteryLevel == 2) R.string.dice_roll_mastery_expertise else R.string.dice_roll_mastery_proficiency, + ability + ), + value = masteryValue * masteryLevel, + ) + ) + } ?: emptyList()) + listOf( + Roll.Bonus( + title = context.getString(R.string.dice_roll_bonus_detail, related), + value = abilityValue.modifier + ) + ) + alterations.map { it.bonus }.flatten(), + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/preview/rememberCharacterSheetPreview.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/preview/rememberCharacterSheetPreview.kt deleted file mode 100644 index c47f8d4..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/preview/rememberCharacterSheetPreview.kt +++ /dev/null @@ -1,191 +0,0 @@ -package com.pixelized.rplexicon.ui.screens.character.preview - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.remember -import com.pixelized.rplexicon.R -import com.pixelized.rplexicon.ui.screens.character.CharacterSheetUio -import com.pixelized.rplexicon.ui.screens.character.composable.LabelPointUio -import com.pixelized.rplexicon.ui.screens.character.composable.ProficiencyUio -import com.pixelized.rplexicon.ui.screens.character.composable.StatUio - -@Stable -@Composable -fun rememberCharacterSheetPreview() = remember { - CharacterSheetUio( - armorClass = LabelPointUio( - label = R.string.character_sheet_title_ca, - value = "15", - ), - hitPoint = LabelPointUio( - label = R.string.character_sheet_title_hp, - value = "125", - max = "/ 150", - ), - speed = LabelPointUio( - label = R.string.character_sheet_title_speed, - value = "30", - max = "m", - ), - initiative = LabelPointUio( - label = R.string.character_sheet_title_initiative, - value = "+2", - ), - stats = listOf( - StatUio( - id = StatUio.ID.STRENGTH, - value = 16, - modifier = 3, - ), - StatUio( - id = StatUio.ID.DEXTERITY, - value = 10, - modifier = 0, - ), - StatUio( - id = StatUio.ID.CONSTITUTION, - value = 16, - modifier = 3, - ), - StatUio( - id = StatUio.ID.INTELLIGENCE, - value = 8, - modifier = 1, - ), - StatUio( - id = StatUio.ID.WISDOM, - value = 14, - modifier = 2, - ), - StatUio( - id = StatUio.ID.CHARISMA, - value = 10, - modifier = 0, - ), - ), - savingThrows = listOf( - ProficiencyUio( - id = ProficiencyUio.ID.STRENGTH_SAVING_THROW, - multiplier = 1, - modifier = 5, - ), - ProficiencyUio( - id = ProficiencyUio.ID.DEXTERITY_SAVING_THROW, - multiplier = 0, - modifier = 0, - ), - ProficiencyUio( - id = ProficiencyUio.ID.CONSTITUTION_SAVING_THROW, - multiplier = 1, - modifier = +5, - ), - ProficiencyUio( - id = ProficiencyUio.ID.INTELLIGENCE_SAVING_THROW, - multiplier = 0, - modifier = -1, - ), - ProficiencyUio( - id = ProficiencyUio.ID.WISDOM_SAVING_THROW, - multiplier = 0, - modifier = +2, - ), - ProficiencyUio( - id = ProficiencyUio.ID.CHARISMA_SAVING_THROW, - multiplier = 0, - modifier = +0, - ), - ), - proficiencies = listOf( - ProficiencyUio( - id = ProficiencyUio.ID.ACROBATICS, - multiplier = 0, - modifier = +0, - ), - ProficiencyUio( - id = ProficiencyUio.ID.ANIMAL_HANDLING, - multiplier = 0, - modifier = 2, - ), - ProficiencyUio( - id = ProficiencyUio.ID.ARCANA, - multiplier = 0, - modifier = -1, - ), - ProficiencyUio( - id = ProficiencyUio.ID.ATHLETICS, - multiplier = 1, - modifier = 5, - ), - ProficiencyUio( - id = ProficiencyUio.ID.DECEPTION, - multiplier = 0, - modifier = 0, - ), - ProficiencyUio( - id = ProficiencyUio.ID.HISTORY, - multiplier = 0, - modifier = -1, - ), - ProficiencyUio( - id = ProficiencyUio.ID.INSIGHT, - multiplier = 1, - modifier = 4, - ), - ProficiencyUio( - id = ProficiencyUio.ID.INTIMIDATION, - multiplier = 1, - modifier = 2, - ), - ProficiencyUio( - id = ProficiencyUio.ID.INVESTIGATION, - multiplier = 0, - modifier = -1, - ), - ProficiencyUio( - id = ProficiencyUio.ID.MEDICINE, - multiplier = 0, - modifier = 2, - ), - ProficiencyUio( - id = ProficiencyUio.ID.NATURE, - multiplier = 0, - modifier = -1, - ), - ProficiencyUio( - id = ProficiencyUio.ID.PERCEPTION, - multiplier = 1, - modifier = 4, - ), - ProficiencyUio( - id = ProficiencyUio.ID.PERFORMANCE, - multiplier = 0, - modifier = 0, - ), - ProficiencyUio( - id = ProficiencyUio.ID.PERSUASION, - multiplier = 0, - modifier = 0, - ), - ProficiencyUio( - id = ProficiencyUio.ID.RELIGION, - multiplier = 0, - modifier = -1, - ), - ProficiencyUio( - id = ProficiencyUio.ID.SLEIGHT_OF_HAND, - multiplier = 0, - modifier = 0, - ), - ProficiencyUio( - id = ProficiencyUio.ID.STEALTH, - multiplier = 0, - modifier = 0, - ), - ProficiencyUio( - id = ProficiencyUio.ID.SURVIVAL, - multiplier = 1, - modifier = 4, - ), - ), - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/preview/rememberWeaponListStatePreview.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/preview/rememberWeaponListStatePreview.kt deleted file mode 100644 index 6121236..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/preview/rememberWeaponListStatePreview.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.pixelized.rplexicon.ui.screens.character.preview - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import com.pixelized.rplexicon.R -import com.pixelized.rplexicon.ui.screens.character.composable.WeaponUio - -@Stable -@Composable -fun rememberWeaponListStatePreview(): State> = remember { - mutableStateOf( - listOf( - WeaponUio( - icon = R.drawable.ic_crossed_swords_24, - name = "Dagger", - type = "Melee weapon", - range = "5 ft reach", - hit = WeaponUio.Dice(icon = R.drawable.ic_d20_24, label = "1d20"), - damage = WeaponUio.Dice(icon = R.drawable.ic_d8_24, label = "1d8"), - ), - WeaponUio( - icon = R.drawable.ic_pocket_bow_24, - name = "Long bow", - type = "Ranged weapon", - range = "30 ft reach", - hit = WeaponUio.Dice(icon = R.drawable.ic_d20_24, label = "1d20+5"), - damage = WeaponUio.Dice( - icon = R.drawable.ic_d8_24, - label = "1d8+3" - ), - ), - ) - ) -} diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconViewModel.kt index b527a06..8ca3423 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconViewModel.kt @@ -12,6 +12,7 @@ import com.pixelized.rplexicon.repository.data.ActionRepository import com.pixelized.rplexicon.repository.data.AlterationRepository import com.pixelized.rplexicon.repository.data.CharacterSheetRepository import com.pixelized.rplexicon.repository.data.LexiconRepository +import com.pixelized.rplexicon.repository.data.SpellRepository import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure import dagger.hilt.android.lifecycle.HiltViewModel @@ -28,6 +29,7 @@ class LexiconViewModel @Inject constructor( private val characterSheetRepository: CharacterSheetRepository, private val alterationRepository: AlterationRepository, private val actionRepository: ActionRepository, + private val spellRepository: SpellRepository, ) : ViewModel() { private val _isLoading = mutableStateOf(false) @@ -97,6 +99,13 @@ class LexiconViewModel @Inject constructor( Log.e(TAG, exception.message, exception) } } + val magicRequest = async { + try { + spellRepository.fetchSpells() + } catch (exception: Exception) { + Log.e(TAG, exception.message, exception) + } + } val lexiconRequest = async { fetchLexicon() } @@ -105,6 +114,7 @@ class LexiconViewModel @Inject constructor( alterationRequest, statusRequest, lexiconRequest, + magicRequest, ) _isLoading.value = false } diff --git a/app/src/main/java/com/pixelized/rplexicon/facotry/displayable/ConvertRollIntoDisplayableFactory.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/ConvertRollIntoDisplayableFactory.kt similarity index 98% rename from app/src/main/java/com/pixelized/rplexicon/facotry/displayable/ConvertRollIntoDisplayableFactory.kt rename to app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/ConvertRollIntoDisplayableFactory.kt index 3ebba2e..6e06433 100644 --- a/app/src/main/java/com/pixelized/rplexicon/facotry/displayable/ConvertRollIntoDisplayableFactory.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/ConvertRollIntoDisplayableFactory.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.facotry.displayable +package com.pixelized.rplexicon.ui.screens.rolls import com.pixelized.rplexicon.model.Roll import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlayViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlayViewModel.kt index 1d0b55a..205835a 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlayViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/RollOverlayViewModel.kt @@ -4,7 +4,6 @@ import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.pixelized.rplexicon.facotry.displayable.ConvertRollIntoDisplayableFactory import com.pixelized.rplexicon.model.Roll import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailScreen.kt new file mode 100644 index 0000000..2fe1abe --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailScreen.kt @@ -0,0 +1,266 @@ +package com.pixelized.rplexicon.ui.screens.spell + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.aspectRatio +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.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.composable.BackgroundImage +import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost +import com.pixelized.rplexicon.ui.theme.LexiconTheme +import com.pixelized.rplexicon.utilitary.extentions.scrollOffset + +@Stable +class SpellDetailUio( + @DrawableRes val icon: Int, + val name: String, + val translated: String, + val level: String, + @StringRes val school: Int, + val castingTime: String, + val range: String, + val requirement: String, + val duration: String, + val description: String, +) + +@Composable +fun SpellDetailScreen( + viewModel: SpellDetailViewModel = hiltViewModel(), +) { + val screen = LocalScreenNavHost.current + Surface( + modifier = Modifier.fillMaxSize(), + ) { + SpellDetailContent( + modifier = Modifier.fillMaxSize(), + spell = viewModel.spell, + onBack = { screen.popBackStack() }, + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) +@Composable +private fun SpellDetailContent( + modifier: Modifier, + state: ScrollState = rememberScrollState(), + spell: State, + onBack: () -> Unit, +) { + Scaffold( + modifier = modifier, + containerColor = Color.Transparent, + topBar = { + TopAppBar( + modifier = Modifier.shadow(elevation = 4.dp), + navigationIcon = { + IconButton(onClick = onBack) { + Icon( + painter = painterResource(id = R.drawable.ic_arrow_back_ios_new_24), + contentDescription = null + ) + } + }, + title = { + Text(text = stringResource(id = R.string.spell_detail_title)) + }, + ) + }, + ) { paddingValues -> + Box( + modifier = Modifier.padding(paddingValues = paddingValues), + ) { + val detail = spell.value + if (detail != null) { + BackgroundImage( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(ratio = 1f) + .scrollOffset(scrollState = state) { -it / 2 }, + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f), + painter = painterResource(id = detail.icon), + ) + Column( + modifier = Modifier + .verticalScroll(state) + .padding(all = 16.dp), + ) { + FlowRow( + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + ) { + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.headlineSmall, + text = detail.name, + ) + Text( + modifier = Modifier.alignByBaseline(), + fontStyle = FontStyle.Italic, + style = MaterialTheme.typography.bodyMedium, + text = detail.translated, + ) + } + + FlowRow( + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + ) { + Text( + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + text = stringResource(id = R.string.spell_detail_school), + ) + Text( + style = MaterialTheme.typography.bodyMedium, + text = stringResource(id = detail.school), + ) + } + + FlowRow( + modifier = Modifier.padding(top = 16.dp), + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + ) { + Text( + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + text = stringResource(id = R.string.spell_detail_level), + ) + Text( + style = MaterialTheme.typography.bodyMedium, + text = detail.level, + ) + } + + FlowRow( + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + ) { + Text( + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + text = stringResource(id = R.string.spell_detail_casting_time), + ) + Text( + style = MaterialTheme.typography.bodyMedium, + text = detail.castingTime, + ) + } + FlowRow( + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + ) { + Text( + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + text = stringResource(id = R.string.spell_detail_range), + ) + Text( + style = MaterialTheme.typography.bodyMedium, + text = detail.range, + ) + } + FlowRow( + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + ) { + Text( + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + text = stringResource(id = R.string.spell_detail_components), + ) + Text( + style = MaterialTheme.typography.bodyMedium, + text = detail.requirement, + ) + } + FlowRow( + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), + ) { + Text( + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + text = stringResource(id = R.string.spell_detail_duration), + ) + Text( + style = MaterialTheme.typography.bodyMedium, + text = detail.duration, + ) + } + Text( + modifier = Modifier.padding(top = 16.dp), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + text = stringResource(id = R.string.spell_detail_description), + ) + Text( + style = MaterialTheme.typography.bodyMedium, + text = detail.description, + ) + } + } + } + } + +} + +@Composable +@Preview(uiMode = UI_MODE_NIGHT_NO) +@Preview(uiMode = UI_MODE_NIGHT_YES) +private fun SpellDetailPreview() { + LexiconTheme { + Surface { + SpellDetailContent( + modifier = Modifier.fillMaxSize(), + spell = remember { + mutableStateOf( + SpellDetailUio( + icon = R.drawable.ic_evocation_24, + name = "Soins", + translated = "Cure Wounds", + level = "1", + school = R.string.spell_school_evocation, + castingTime = "1 action", + range = "contact", + requirement = "V, S", + duration = "instantanée", + description = "La créature que vous touchez récupère un nombre de points de vie égal à 1d8 + votre modificateur de caractéristique d'incantation. Ce sort n'a aucun effet sur les morts-vivants et les créatures artificielles.\nÀ plus haut niveau. Si vous lancez ce sort en utilisant un emplacement de niveau 2 ou supérieur, les soins augmentent de 1d8 par niveau au-delà du niveau 1.", + ) + ) + }, + onBack = { }, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailViewModel.kt new file mode 100644 index 0000000..9a80476 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/spell/SpellDetailViewModel.kt @@ -0,0 +1,44 @@ +package com.pixelized.rplexicon.ui.screens.spell + +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import com.pixelized.rplexicon.repository.data.SpellRepository +import com.pixelized.rplexicon.ui.navigation.screens.spellDetailArgument +import com.pixelized.rplexicon.utilitary.extentions.local.icon +import com.pixelized.rplexicon.utilitary.extentions.local.label +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class SpellDetailViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + repository: SpellRepository +) : ViewModel() { + val spell: State + + init { + val argument = savedStateHandle.spellDetailArgument + + spell = mutableStateOf( + repository.findAssignedSpell( + character = argument.character, + spell = argument.spell, + )?.let { + SpellDetailUio( + icon = it.spell.school.icon, + name = it.spell.name, + translated = it.spell.originalName, + level = "${it.spell.level}", + school = it.spell.school.label, + castingTime = it.spell.castingTime, + range = it.spell.range, + requirement = it.spell.requirement, + duration = it.spell.duration, + description = it.spell.description, + ) + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/IntEx.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/IntEx.kt index 56e8525..88fbdce 100644 --- a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/IntEx.kt +++ b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/IntEx.kt @@ -1,5 +1,7 @@ package com.pixelized.rplexicon.utilitary.extentions +import android.net.Uri +import com.pixelized.rplexicon.BuildConfig import com.pixelized.rplexicon.R import kotlin.math.abs import kotlin.math.floor @@ -27,4 +29,7 @@ fun List.toLabel(): String = when (size) { 0 -> "" 1 -> "${abs(this[0])}" else -> joinToString(" ") { "${it.signLabel} ${abs(it)}" }.substring(2) -} \ No newline at end of file +} + +val Int.uri: String + get() = Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/$this").toString() \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/AlterationEx.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/AlterationEx.kt new file mode 100644 index 0000000..5801726 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/AlterationEx.kt @@ -0,0 +1,16 @@ +package com.pixelized.rplexicon.utilitary.extentions.local + +import com.pixelized.rplexicon.model.Alteration +import com.pixelized.rplexicon.model.Property + +fun List.status(): Map> { + val status = hashMapOf>() + + forEach { alteration -> + alteration.status.forEach { statut -> + status.getOrPut(statut.key) { mutableListOf() }.add(statut.value) + } + } + + return status +} diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/CharacterSheetEx.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/CharacterSheetEx.kt new file mode 100644 index 0000000..d0d6ba3 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/CharacterSheetEx.kt @@ -0,0 +1,45 @@ +package com.pixelized.rplexicon.utilitary.extentions.local + +import com.pixelized.rplexicon.model.CharacterSheet +import com.pixelized.rplexicon.model.Counter + +fun CharacterSheet.spell(level: Int): Counter? = when (level) { + 1 -> spell1 + 2 -> spell2 + 3 -> spell3 + 4 -> spell4 + 5 -> spell5 + 6 -> spell6 + 7 -> spell7 + 8 -> spell8 + 9 -> spell9 + else -> null +} + +fun CharacterSheet.highestSpellLevel(): Int = when { + spell9 != null -> 9 + spell8 != null -> 8 + spell6 != null -> 6 + spell7 != null -> 7 + spell5 != null -> 5 + spell4 != null -> 4 + spell3 != null -> 3 + spell2 != null -> 2 + spell1 != null -> 1 + else -> 0 +} + +fun CharacterSheet.firstSpellSlot(): Int? { + return when { + this.spell1 != null -> 1 + this.spell2 != null -> 2 + this.spell3 != null -> 3 + this.spell4 != null -> 4 + this.spell5 != null -> 5 + this.spell6 != null -> 6 + this.spell7 != null -> 7 + this.spell8 != null -> 8 + this.spell9 != null -> 9 + else -> null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/PropertyEx.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/PropertyEx.kt new file mode 100644 index 0000000..ebd87ac --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/PropertyEx.kt @@ -0,0 +1,50 @@ +package com.pixelized.rplexicon.utilitary.extentions.local + +import com.pixelized.rplexicon.model.CharacterSheet +import com.pixelized.rplexicon.model.Property +import com.pixelized.rplexicon.utilitary.extentions.modifier + +fun Property.toValue( + character: CharacterSheet, +): Int { + return when (this) { + Property.PROFICIENCY -> character.proficiency + Property.HIT_POINT -> 0 // TODO + Property.ARMOR_CLASS -> 0 // TODO + Property.INITIATIVE -> 0 // TODO + Property.STRENGTH -> character.strength.modifier // TODO have a dedicated modifier property. + Property.DEXTERITY -> character.dexterity.modifier // TODO have a dedicated modifier property. + Property.CONSTITUTION -> character.constitution.modifier // TODO have a dedicated modifier property. + Property.INTELLIGENCE -> character.intelligence.modifier // TODO have a dedicated modifier property. + Property.WISDOM -> character.wisdom.modifier // TODO have a dedicated modifier property. + Property.CHARISMA -> character.charisma.modifier // TODO have a dedicated modifier property. + Property.STRENGTH_SAVING_THROW -> character.strengthSavingThrows + Property.DEXTERITY_SAVING_THROW -> character.dexteritySavingThrows + Property.CONSTITUTION_SAVING_THROW -> character.constitutionSavingThrows + Property.INTELLIGENCE_SAVING_THROW -> character.intelligenceSavingThrows + Property.WISDOM_SAVING_THROW -> character.wisdomSavingThrows + Property.CHARISMA_SAVING_THROW -> character.charismaSavingThrows + Property.ACROBATICS -> character.acrobatics + Property.ANIMAL_HANDLING -> character.animalHandling + Property.ARCANA -> character.arcana + Property.ATHLETICS -> character.athletics + Property.DECEPTION -> character.deception + Property.HISTORY -> character.history + Property.INSIGHT -> character.insight + Property.INTIMIDATION -> character.intimidation + Property.INVESTIGATION -> character.investigation + Property.MEDICINE -> character.medicine + Property.NATURE -> character.nature + Property.PERCEPTION -> character.perception + Property.PERFORMANCE -> character.performance + Property.PERSUASION -> character.persuasion + Property.RELIGION -> character.religion + Property.SLEIGHT_OF_HAND -> character.sleightOfHand + Property.STEALTH -> character.stealth + Property.SURVIVAL -> character.survival + Property.PHYSICAL_MELEE_ATTACK -> 0 // TODO + Property.PHYSICAL_MELEE_DAMAGE -> 0 // TODO + Property.PHYSICAL_RANGE_ATTACK -> 0 // TODO + Property.PHYSICAL_RANGE_DAMAGE -> 0 // TODO + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/SchoolEx.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/SchoolEx.kt new file mode 100644 index 0000000..442310f --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/SchoolEx.kt @@ -0,0 +1,28 @@ +package com.pixelized.rplexicon.utilitary.extentions.local + +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.model.Spell + +val Spell.School.icon: Int + get() = when (this) { + Spell.School.ABJURATION -> R.drawable.ic_abjuration_24 + Spell.School.DIVINATION -> R.drawable.ic_divination_24 + Spell.School.ENCHANTMENT -> R.drawable.ic_enchantment_24 + Spell.School.EVOCATION -> R.drawable.ic_evocation_24 + Spell.School.ILLUSION -> R.drawable.ic_illusion_24 + Spell.School.CONJURATION -> R.drawable.ic_conjuration_24 + Spell.School.NECROMANCY -> R.drawable.ic_necromancy_24 + Spell.School.TRANSMUTATION -> R.drawable.ic_transmutation_24 + } + +val Spell.School.label: Int + get() = when (this) { + Spell.School.ABJURATION -> R.string.spell_school_abjuration + Spell.School.DIVINATION -> R.string.spell_school_divination + Spell.School.ENCHANTMENT -> R.string.spell_school_enchantment + Spell.School.EVOCATION -> R.string.spell_school_evocation + Spell.School.ILLUSION -> R.string.spell_school_illusion + Spell.School.CONJURATION -> R.string.spell_school_conjuration + Spell.School.NECROMANCY -> R.string.spell_school_necromancy + Spell.School.TRANSMUTATION -> R.string.spell_school_transmutation + } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/SheetEx.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/SheetEx.kt similarity index 94% rename from app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/SheetEx.kt rename to app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/SheetEx.kt index 33df48e..8c10979 100644 --- a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/SheetEx.kt +++ b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/SheetEx.kt @@ -1,4 +1,4 @@ -package com.pixelized.rplexicon.utilitary.extentions +package com.pixelized.rplexicon.utilitary.extentions.local import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/ThrowEx.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/ThrowEx.kt new file mode 100644 index 0000000..6f7dc7c --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/local/ThrowEx.kt @@ -0,0 +1,20 @@ +package com.pixelized.rplexicon.utilitary.extentions.local + +import com.pixelized.rplexicon.model.CharacterSheet +import com.pixelized.rplexicon.model.Throw + +/** + * Helper method to build a readable String from a Throw. + * Create a string following the format "amount 'd' faces '+' modifiers + amount * level 'd' faces" + * where modifier is a integer, sum of all modifiers values. + * ex : for Sleep level 2 -> 5d8+4 + 2d8 + */ +fun Throw.toString(character: CharacterSheet, level: Int = 0): String? { + val modifierLabel = modifier + .sumOf { modifier -> modifier.toValue(character = character) } + .let { if (it > 0) "+${it * level}" else "" } + return when (level > 0) { + true -> "${amount * level}d${faces}${modifierLabel}" + else -> null + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_abjuration_24.xml b/app/src/main/res/drawable/ic_abjuration_24.xml new file mode 100644 index 0000000..59a8692 --- /dev/null +++ b/app/src/main/res/drawable/ic_abjuration_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_conjuration_24.xml b/app/src/main/res/drawable/ic_conjuration_24.xml new file mode 100644 index 0000000..b40e186 --- /dev/null +++ b/app/src/main/res/drawable/ic_conjuration_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_divination_24.xml b/app/src/main/res/drawable/ic_divination_24.xml new file mode 100644 index 0000000..c747dc5 --- /dev/null +++ b/app/src/main/res/drawable/ic_divination_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_enchantment_24.xml b/app/src/main/res/drawable/ic_enchantment_24.xml new file mode 100644 index 0000000..d1b1acf --- /dev/null +++ b/app/src/main/res/drawable/ic_enchantment_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_evocation_24.xml b/app/src/main/res/drawable/ic_evocation_24.xml new file mode 100644 index 0000000..99b05d2 --- /dev/null +++ b/app/src/main/res/drawable/ic_evocation_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_illusion_24.xml b/app/src/main/res/drawable/ic_illusion_24.xml new file mode 100644 index 0000000..23aeb37 --- /dev/null +++ b/app/src/main/res/drawable/ic_illusion_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_necromancy_24.xml b/app/src/main/res/drawable/ic_necromancy_24.xml new file mode 100644 index 0000000..e7e07ee --- /dev/null +++ b/app/src/main/res/drawable/ic_necromancy_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_transmutation_24.xml b/app/src/main/res/drawable/ic_transmutation_24.xml new file mode 100644 index 0000000..9df151c --- /dev/null +++ b/app/src/main/res/drawable/ic_transmutation_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index a5caec4..da2989d 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -26,6 +26,15 @@ Goliath Indéterminé + Abjuration + Divination + Enchantement + Évocation + Illusion + Invocation + Nécromancie + Transmutation + Se connecter avec Lexique @@ -100,6 +109,9 @@ Escamotage Discrétion Survie + Lancer + Cantrip + Sort de niveau %1$s Maîtrise \"%1$s\" Expertise \"%1$s\" @@ -108,6 +120,7 @@ Bonus de Maîtrise Bonus d\'Expertise TEST \"%1$s\" + Lancer : \"%1$s\" Test \"%1$s\" Bonus \"%1$s\" Jet d\'attaque : \"%1$s\" @@ -117,4 +130,17 @@ JET DE SAUVEGARDE : %1$s Sauvegarde de \"%1$s\" + + Attaque de corps à corps + Attaque à distance + Sortilège + Niveau + École + Temps d\'incantation + Portée + Composantes + Durée + Description + Sort de niveau %1$s + Disponible : \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f63ddd9..ef4e9a3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,6 +26,15 @@ Goliath Undetermined + Abjuration + Divination + Enchantment + Evocation + Illusion + Conjuration + Necromancy + Transmutation + Sign in with Lexicon @@ -100,6 +109,9 @@ Sleight Of Hand Stealth Survival + Cast + Cantrip + Spell level %1$s %1$s proficiency %1$s expertise @@ -107,6 +119,7 @@ Proficiency bonus Expertise bonus + Cast %1$s %1$s CHECK %1$s check %1$s bonus @@ -117,4 +130,17 @@ %1$s SAVING THROW %1$s save + + Melee physical attack + Range physical attack + Spell + Level + School + Casting Time + Range + Components + Duration + Description + Spell level %1$s + Available: \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index aa388f0..655b559 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,10 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { id("com.android.application") version "8.1.1" apply false - id("org.jetbrains.kotlin.android") version "1.9.0" apply false + id("org.jetbrains.kotlin.android") version "1.9.10" apply false id("com.google.gms.google-services") version "4.3.14" apply false id("com.google.dagger.hilt.android") version "2.44" apply false id("com.google.firebase.crashlytics") version "2.9.7" apply false - id("org.jetbrains.kotlin.kapt") version "1.9.0" apply false - id("com.google.devtools.ksp") version "1.9.0-1.0.13" apply false + id("org.jetbrains.kotlin.kapt") version "1.9.10" apply false + id("com.google.devtools.ksp") version "1.9.10-1.0.13" apply false }