From cd921ac16ffefed5eb4ddd68ff7d17c27bcbe3e5 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Tue, 24 Oct 2023 18:08:25 +0200 Subject: [PATCH] Add Object action in the character action sheet. --- .../pixelized/rplexicon/LauncherViewModel.kt | 12 +- .../rplexicon/business/DiceThrowUseCase.kt | 32 ++++- .../pixelized/rplexicon/model/DiceThrow.kt | 1 + .../pixelized/rplexicon/model/ObjectAction.kt | 6 + .../com/pixelized/rplexicon/model/Throw.kt | 1 + .../rplexicon/repository/data/Sheets.kt | 1 + .../data/character/ObjectActionRepository.kt | 40 ++++++ .../repository/parser/ObjectActionParser.kt | 43 +++++++ .../repository/parser/ThrowParser.kt | 8 +- .../repository/parser/roll/FlatValueParser.kt | 7 +- .../ui/composable/error/FetchErrorUio.kt | 2 + .../screens/character/CharacterSheetScreen.kt | 9 ++ .../character/CharacterSheetViewModel.kt | 14 ++- .../composable/actions/ObjectItem.kt | 114 ++++++++++++++++++ .../preview/rememberObjectListStatePreview.kt | 20 +++ .../character/pages/actions/ActionsPage.kt | 41 ++++++- .../pages/actions/ObjectsViewModel.kt | 60 +++++++++ .../rolls/factory/AlterationFactory.kt | 2 + .../ui/screens/rolls/factory/DiceFactory.kt | 9 ++ app/src/main/res/values-fr/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 21 files changed, 416 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/pixelized/rplexicon/model/ObjectAction.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/repository/data/character/ObjectActionRepository.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/repository/parser/ObjectActionParser.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/ObjectItem.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberObjectListStatePreview.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ObjectsViewModel.kt diff --git a/app/src/main/java/com/pixelized/rplexicon/LauncherViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/LauncherViewModel.kt index f107278..8cbf660 100644 --- a/app/src/main/java/com/pixelized/rplexicon/LauncherViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/LauncherViewModel.kt @@ -12,6 +12,7 @@ import com.pixelized.rplexicon.repository.data.character.CharacterSheetRepositor import com.pixelized.rplexicon.repository.data.character.DescriptionRepository import com.pixelized.rplexicon.repository.data.character.EquipmentRepository import com.pixelized.rplexicon.repository.data.character.InventoryRepository +import com.pixelized.rplexicon.repository.data.character.ObjectActionRepository import com.pixelized.rplexicon.repository.data.character.SkillRepository import com.pixelized.rplexicon.repository.data.character.SpellRepository import com.pixelized.rplexicon.repository.data.lexicon.LexiconRepository @@ -35,6 +36,7 @@ class LauncherViewModel @Inject constructor( alterationRepository: AlterationRepository, characterSheetRepository: CharacterSheetRepository, actionRepository: ActionRepository, + objectActionRepository: ObjectActionRepository, spellRepository: SpellRepository, skillRepository: SkillRepository, descriptionRepository: DescriptionRepository, @@ -124,6 +126,14 @@ class LauncherViewModel @Inject constructor( _error.emit(FetchErrorUio.Structure(type = Type.ACTION)) } } + val objects = async { + try { + objectActionRepository.fetchObjectAction() + } catch (exception: Exception) { + Log.e(TAG, exception.message, exception) + _error.emit(FetchErrorUio.Structure(type = Type.OBJECT)) + } + } val spell = async { try { spellRepository.fetchSpells() @@ -142,7 +152,7 @@ class LauncherViewModel @Inject constructor( } awaitAll(lexicon, location, quest) - awaitAll(description, inventory, equipment, alteration, action, spell, skill) + awaitAll(description, inventory, equipment, alteration, action, objects, spell, skill) isLoading = false } diff --git a/app/src/main/java/com/pixelized/rplexicon/business/DiceThrowUseCase.kt b/app/src/main/java/com/pixelized/rplexicon/business/DiceThrowUseCase.kt index 8c523b2..596165b 100644 --- a/app/src/main/java/com/pixelized/rplexicon/business/DiceThrowUseCase.kt +++ b/app/src/main/java/com/pixelized/rplexicon/business/DiceThrowUseCase.kt @@ -13,6 +13,7 @@ import com.pixelized.rplexicon.model.Throw import com.pixelized.rplexicon.repository.data.character.ActionRepository import com.pixelized.rplexicon.repository.data.character.AlterationRepository import com.pixelized.rplexicon.repository.data.character.CharacterSheetRepository +import com.pixelized.rplexicon.repository.data.character.ObjectActionRepository import com.pixelized.rplexicon.repository.data.character.SkillRepository import com.pixelized.rplexicon.repository.data.character.SpellRepository import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio @@ -42,6 +43,7 @@ class DiceThrowUseCase @Inject constructor( private val application: Application, private val characterSheetRepository: CharacterSheetRepository, private val actionRepository: ActionRepository, + private val objectRepository: ObjectActionRepository, private val spellRepository: SpellRepository, private val skillRepository: SkillRepository, private val alterationRepository: AlterationRepository, @@ -393,6 +395,21 @@ class DiceThrowUseCase @Inject constructor( ) } + is DiceThrow.Object -> { + val action = objectRepository.find( + character = diceThrow.character, + item = diceThrow.item, + ) + actionThrow( + character = sheet, + action = diceThrow.item, + diceThrow = action?.effect, + title = { getString(R.string.dice_roll_attack_hit_title, it) }, + alterations = emptyMap(), + ability = null, + ) + } + is DiceThrow.SpellAttack -> { val spell = spellRepository.find( character = diceThrow.character, @@ -664,7 +681,7 @@ class DiceThrowUseCase @Inject constructor( diceThrow: Throw?, title: Context.(action: String) -> String, alterations: Map>, - ability: Property, + ability: Property?, ): DiceThrowResult { // retrieve some wording. val titleString = title(application, action) @@ -778,6 +795,17 @@ class DiceThrowUseCase @Inject constructor( } } ?: emptyList() + // check for flat dice bonus (ex: healing potion 2d4 + 2) + val flatBonus = diceThrow?.flat?.takeIf { it > 0 }?.let { + allValue.add(it) + listOf( + ThrowsCardUio.Detail( + title = application.getString(R.string.dice_roll_bonus_detail, action), + result = "$it", + ) + ) + }?: emptyList() + // build the result. return DiceThrowResult( dice = RollDiceUio( @@ -812,7 +840,7 @@ class DiceThrowUseCase @Inject constructor( ), result = "${result.value}", ), - ) + diceAlterationBonus + flatAlterationBonus + relatedStatBonus, + ) + diceAlterationBonus + flatAlterationBonus + relatedStatBonus + flatBonus, ) ) } diff --git a/app/src/main/java/com/pixelized/rplexicon/model/DiceThrow.kt b/app/src/main/java/com/pixelized/rplexicon/model/DiceThrow.kt index ab0b69e..1d46fa2 100644 --- a/app/src/main/java/com/pixelized/rplexicon/model/DiceThrow.kt +++ b/app/src/main/java/com/pixelized/rplexicon/model/DiceThrow.kt @@ -40,4 +40,5 @@ sealed class DiceThrow(val character: String) { class SpellDamage(character: String, val spell: String) : DiceThrow(character) class SpellEffect(character: String, val spell: String, val level: Int) : DiceThrow(character) class Skill(character: String, val skill: String) : DiceThrow(character) + class Object(character: String, val item: String) : DiceThrow(character) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/model/ObjectAction.kt b/app/src/main/java/com/pixelized/rplexicon/model/ObjectAction.kt new file mode 100644 index 0000000..5f63a0d --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/model/ObjectAction.kt @@ -0,0 +1,6 @@ +package com.pixelized.rplexicon.model + +data class ObjectAction( + val name: String, + val effect: Throw?, +) \ 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 index 561bd64..d40b1ca 100644 --- a/app/src/main/java/com/pixelized/rplexicon/model/Throw.kt +++ b/app/src/main/java/com/pixelized/rplexicon/model/Throw.kt @@ -3,5 +3,6 @@ package com.pixelized.rplexicon.model class Throw( val amount: Int, val faces: Int, + val flat: Int, val modifier: List, ) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/data/Sheets.kt b/app/src/main/java/com/pixelized/rplexicon/repository/data/Sheets.kt index a28ea92..f2e232a 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/data/Sheets.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/data/Sheets.kt @@ -14,6 +14,7 @@ object CharacterBinder { const val CHARACTER = "Feuille de personnage" const val ATTACK = "Attaques" + const val OBJECT = "Objets" const val MAGIC = "Magies" const val SKILL = "Capacités" const val MAGIC_LEXICON = "Lexique magique" diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/data/character/ObjectActionRepository.kt b/app/src/main/java/com/pixelized/rplexicon/repository/data/character/ObjectActionRepository.kt new file mode 100644 index 0000000..4749c5b --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/repository/data/character/ObjectActionRepository.kt @@ -0,0 +1,40 @@ +package com.pixelized.rplexicon.repository.data.character + +import com.pixelized.rplexicon.model.ObjectAction +import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository +import com.pixelized.rplexicon.repository.data.CharacterBinder +import com.pixelized.rplexicon.repository.parser.ObjectActionParser +import com.pixelized.rplexicon.utilitary.Update +import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ObjectActionRepository @Inject constructor( + private val googleRepository: GoogleSheetServiceRepository, + private val parser: ObjectActionParser +) { + private val _data = MutableStateFlow>>(emptyMap()) + val data: StateFlow>> get() = _data + + var lastSuccessFullUpdate: Update = Update.INITIAL + private set + + fun find(character: String?): List = + _data.value[character] ?: emptyList() + + fun find(character: String?, item: String): ObjectAction? = + find(character).firstOrNull { it.name == item } + + @Throws(IncompatibleSheetStructure::class, Exception::class) + suspend fun fetchObjectAction() { + googleRepository.fetch { sheet -> + val request = sheet.get(CharacterBinder.ID, CharacterBinder.OBJECT) + val data = parser.parse(data = request.execute()) + _data.tryEmit(data) + lastSuccessFullUpdate = Update.currentTime() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/parser/ObjectActionParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/ObjectActionParser.kt new file mode 100644 index 0000000..b514acb --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/ObjectActionParser.kt @@ -0,0 +1,43 @@ +package com.pixelized.rplexicon.repository.parser + +import com.google.api.services.sheets.v4.model.ValueRange +import com.pixelized.rplexicon.model.ObjectAction +import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure +import javax.inject.Inject + +class ObjectActionParser @Inject constructor( + private val throwParser: ThrowParser, +) { + @Throws(IncompatibleSheetStructure::class) + fun parse(data: ValueRange): Map> = parserScope { + val objects = hashMapOf>() + + data.forEachRow { index, row -> + when { + index == 0 -> updateStructure(row = row, columns = COLUMNS) + + row.isNotEmpty() -> { + val character = row[0]?.toItem() + val name = row.parse(column = NAME) + val effect = throwParser.parse(value = row.parse(column = EFFECT)) + if (character != null && name != null && effect != null) { + objects.getOrPut(character) { mutableListOf() }.add( + ObjectAction( + name = name, + effect = effect, + ) + ) + } + } + } + } + + return@parserScope objects + } + + companion object { + private const val NAME = "Nom" + private const val EFFECT = "Effet" + private val COLUMNS = listOf(NAME, EFFECT) + } +} \ No newline at end of file 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 index 2476d85..ce789a6 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/parser/ThrowParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/ThrowParser.kt @@ -1,12 +1,14 @@ 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 com.pixelized.rplexicon.repository.parser.roll.DiceParser +import com.pixelized.rplexicon.repository.parser.roll.FlatValueParser +import com.pixelized.rplexicon.repository.parser.roll.ModifierParser import javax.inject.Inject class ThrowParser @Inject constructor( private val diceParser: DiceParser, + private val flatValueParser: FlatValueParser, private val modifierParser: ModifierParser, ) { fun parse(value: String?): Throw? { @@ -14,9 +16,11 @@ class ThrowParser @Inject constructor( val dice = diceParser.findAll(value = value).firstOrNull() if (dice != null) { val modifier = modifierParser.findAll(value = value) + val flat = flatValueParser.parse(value = value) return Throw( amount = dice.count, faces = dice.faces, + flat = flat, modifier = modifier, ) } diff --git a/app/src/main/java/com/pixelized/rplexicon/repository/parser/roll/FlatValueParser.kt b/app/src/main/java/com/pixelized/rplexicon/repository/parser/roll/FlatValueParser.kt index 05a63cf..9479e2b 100644 --- a/app/src/main/java/com/pixelized/rplexicon/repository/parser/roll/FlatValueParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/repository/parser/roll/FlatValueParser.kt @@ -6,7 +6,12 @@ import javax.inject.Inject class FlatValueParser @Inject constructor() { companion object { - private val FLAT_REGEX = Regex("(? { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/composable/error/FetchErrorUio.kt b/app/src/main/java/com/pixelized/rplexicon/ui/composable/error/FetchErrorUio.kt index 6d15567..ef4ea2d 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/composable/error/FetchErrorUio.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/composable/error/FetchErrorUio.kt @@ -28,6 +28,7 @@ sealed class FetchErrorUio { DESCRIPTION, EQUIPMENT, INVENTORY, + OBJECT, SKILL, SPELL, LEXICON, @@ -53,6 +54,7 @@ fun HandleFetchError( FetchErrorUio.Structure.Type.DESCRIPTION -> R.string.error_structure_description FetchErrorUio.Structure.Type.EQUIPMENT -> R.string.error_structure_equipment FetchErrorUio.Structure.Type.INVENTORY -> R.string.error_structure_inventory + FetchErrorUio.Structure.Type.OBJECT -> R.string.error_structure_objects FetchErrorUio.Structure.Type.SKILL -> R.string.error_structure_skill FetchErrorUio.Structure.Type.SPELL -> R.string.error_structure_spell FetchErrorUio.Structure.Type.LEXICON -> R.string.error_structure_lexicon 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 2906535..a0ec807 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 @@ -68,6 +68,9 @@ import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio 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.AttacksViewModel +import com.pixelized.rplexicon.ui.screens.character.pages.actions.HeaderViewModel +import com.pixelized.rplexicon.ui.screens.character.pages.actions.ObjectsViewModel +import com.pixelized.rplexicon.ui.screens.character.pages.actions.SkillsViewModel import com.pixelized.rplexicon.ui.screens.character.pages.actions.SpellsViewModel import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationPage import com.pixelized.rplexicon.ui.screens.character.pages.alteration.AlterationPagePreview @@ -93,8 +96,11 @@ fun CharacterSheetScreen( viewModel: CharacterSheetViewModel = hiltViewModel(), inventoryViewModel: InventoryViewModel = hiltViewModel(), proficiencyViewModel: ProficiencyViewModel = hiltViewModel(), + headerViewModel: HeaderViewModel = hiltViewModel(), attacksViewModel: AttacksViewModel = hiltViewModel(), + objectsViewModel: ObjectsViewModel = hiltViewModel(), spellsViewModel: SpellsViewModel = hiltViewModel(), + skillViewModel: SkillsViewModel = hiltViewModel(), alterationsViewModel: AlterationViewModel = hiltViewModel(), ) { val snack = LocalSnack.current @@ -153,8 +159,11 @@ fun CharacterSheetScreen( actions = { ActionPage( sheetState = sheetState, + headerViewModel = headerViewModel, attacksViewModel = attacksViewModel, + objectsViewModel = objectsViewModel, spellsViewModel = spellsViewModel, + skillViewModel = skillViewModel, ) }, alterations = { 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 9d2eea6..41b22ce 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 @@ -13,6 +13,7 @@ import com.pixelized.rplexicon.repository.data.character.CharacterSheetRepositor import com.pixelized.rplexicon.repository.data.character.DescriptionRepository import com.pixelized.rplexicon.repository.data.character.EquipmentRepository import com.pixelized.rplexicon.repository.data.character.InventoryRepository +import com.pixelized.rplexicon.repository.data.character.ObjectActionRepository import com.pixelized.rplexicon.repository.data.character.SkillRepository import com.pixelized.rplexicon.repository.data.character.SpellRepository import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio @@ -35,6 +36,7 @@ class CharacterSheetViewModel @Inject constructor( private val inventoryRepository: InventoryRepository, private val equipmentRepository: EquipmentRepository, private val actionRepository: ActionRepository, + private val objectRepository: ObjectActionRepository, private val spellRepository: SpellRepository, private val skillRepository: SkillRepository, private val firebaseRepository: FirebaseRepository, @@ -124,6 +126,16 @@ class CharacterSheetViewModel @Inject constructor( } } } + val objects = async { + if (force || objectRepository.lastSuccessFullUpdate.shouldUpdate()) { + try { + objectRepository.fetchObjectAction() + } catch (exception: Exception) { + Log.e(TAG, exception.message, exception) + _error.emit(FetchErrorUio.Structure(type = Type.OBJECT)) + } + } + } val spells = async { if (force || spellRepository.lastSuccessFullUpdate.shouldUpdate()) { try { @@ -144,7 +156,7 @@ class CharacterSheetViewModel @Inject constructor( } } } - awaitAll(description, alterations, inventory, equipment, actions, spells, skill) + awaitAll(description, alterations, inventory, equipment, actions, objects, spells, skill) _isLoading.value = false } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/ObjectItem.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/ObjectItem.kt new file mode 100644 index 0000000..d8fcf18 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/actions/ObjectItem.kt @@ -0,0 +1,114 @@ +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.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.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +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.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.unit.dp +import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.theme.LexiconTheme + +@Stable +data class ObjectItemUio( + val name: String, + val original: String?, +) + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun ObjectItem( + modifier: Modifier = Modifier, + padding: PaddingValues = PaddingValues(top = 4.dp, bottom = 4.dp, start = 16.dp, end = 16.dp), + item: ObjectItemUio, + onObject: (String) -> Unit, + onUse: (String) -> Unit, +) { + Row( + modifier = Modifier + .clickable { onObject(item.name) } + .padding(paddingValues = padding) + .then(other = modifier), + horizontalArrangement = Arrangement.spacedBy(space = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + 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 = item.name, + ) + item.original?.let { + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.labelSmall, + fontWeight = FontWeight.Light, + fontStyle = FontStyle.Italic, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = it, + ) + } + } + } + + OutlinedButton( + border = BorderStroke( + width = 1.dp, + color = MaterialTheme.colorScheme.primary, + ), + onClick = { onUse(item.name) }, + ) { + Text( + text = stringResource(id = R.string.character_sheet_action_spell_cast), + ) + } + } +} + +@Composable +@Preview(uiMode = UI_MODE_NIGHT_NO) +@Preview(uiMode = UI_MODE_NIGHT_YES) +private fun ObjectItemPreview() { + LexiconTheme { + Surface { + ObjectItem( + modifier = Modifier.fillMaxWidth(), + item = ObjectItemUio( + name = "Potion de guérison", + original = "Healing potion", + ), + onObject = { }, + onUse = { }, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberObjectListStatePreview.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberObjectListStatePreview.kt new file mode 100644 index 0000000..e21db0e --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/composable/preview/rememberObjectListStatePreview.kt @@ -0,0 +1,20 @@ +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.ui.screens.character.composable.actions.ObjectItemUio + +@Composable +@Stable +fun rememberObjectListStatePreview() = remember { + mutableStateOf( + listOf( + ObjectItemUio( + name = "Potion de guérison", + original = "Healing potion", + ), + ) + ) +} \ 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 index 6206f30..726665b 100644 --- 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 @@ -17,13 +17,10 @@ import androidx.compose.material.ModalBottomSheetState import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import androidx.compose.runtime.remember 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.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.LocalRollOverlay import com.pixelized.rplexicon.R @@ -35,6 +32,8 @@ 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.GenericHeader +import com.pixelized.rplexicon.ui.screens.character.composable.actions.ObjectItem +import com.pixelized.rplexicon.ui.screens.character.composable.actions.ObjectItemUio import com.pixelized.rplexicon.ui.screens.character.composable.actions.SkillItem import com.pixelized.rplexicon.ui.screens.character.composable.actions.SkillItemUio import com.pixelized.rplexicon.ui.screens.character.composable.actions.Spell @@ -46,6 +45,7 @@ import com.pixelized.rplexicon.ui.screens.character.composable.character.Charact import com.pixelized.rplexicon.ui.screens.character.composable.character.CharacterSheetHeaderUio import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberAttackListStatePreview import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberCharacterHeaderStatePreview +import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberObjectListStatePreview import com.pixelized.rplexicon.ui.screens.character.composable.preview.rememberSpellListStatePreview import com.pixelized.rplexicon.ui.theme.LexiconTheme import kotlinx.coroutines.launch @@ -56,6 +56,7 @@ fun ActionPage( sheetState: ModalBottomSheetState, headerViewModel: HeaderViewModel = hiltViewModel(), attacksViewModel: AttacksViewModel = hiltViewModel(), + objectsViewModel: ObjectsViewModel = hiltViewModel(), spellsViewModel: SpellsViewModel = hiltViewModel(), skillViewModel: SkillsViewModel = hiltViewModel(), ) { @@ -67,6 +68,7 @@ fun ActionPage( modifier = Modifier.fillMaxSize(), header = headerViewModel.header, attacks = attacksViewModel.attacks, + objects = objectsViewModel.objects, tokens = skillViewModel.skills, spells = spellsViewModel.spells, onHitPoint = headerViewModel::toggleHitPointDialog, @@ -82,6 +84,15 @@ fun ActionPage( overlay.showOverlay() } }, + onObject = { + + }, + onUseObject = { id -> + objectsViewModel.onUse(id)?.let { + overlay.prepareRoll(diceThrow = it) + overlay.showOverlay() + } + }, onSkillCount = { skillViewModel.showSkillEditDialog(item = it) }, @@ -151,11 +162,14 @@ fun ActionsPageContent( lazyListState: LazyListState = rememberLazyListState(), header: State, attacks: State>, + objects: State>, tokens: State>, spells: State>>>, onHitPoint: () -> Unit, onAttackHit: (id: String) -> Unit, onAttackDamage: (id: String) -> Unit, + onObject: (id: String) -> Unit, + onUseObject: (id: String) -> Unit, onSkillThrow: (SkillItemUio) -> Unit, onSkillCount: (SkillItemUio) -> Unit, onSkillInfo: (SkillItemUio) -> Unit, @@ -195,6 +209,24 @@ fun ActionsPageContent( } } + if (objects.value.isNotEmpty()) { + stickyHeader { + GenericHeader( + label = R.string.character_sheet_title_objects, + ) + } + items(items = objects.value) { + ObjectItem( + item = it, + onObject = onObject, + onUse = onUseObject, + ) + } + items(count = 1) { + Spacer(modifier = Modifier.height(height = 16.dp)) + } + } + if (tokens.value.isNotEmpty()) { stickyHeader { GenericHeader( @@ -248,11 +280,14 @@ fun ActionPagePreview() { modifier = Modifier.fillMaxSize(), header = rememberCharacterHeaderStatePreview(), attacks = rememberAttackListStatePreview(), + objects = rememberObjectListStatePreview(), tokens = rememberTokenListStatePreview(), spells = rememberSpellListStatePreview(), onHitPoint = { }, onAttackHit = { }, onAttackDamage = { }, + onObject = { }, + onUseObject = { }, onSkillCount = { }, onSkillThrow = { }, onSkillInfo = { }, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ObjectsViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ObjectsViewModel.kt new file mode 100644 index 0000000..93c7b47 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/character/pages/actions/ObjectsViewModel.kt @@ -0,0 +1,60 @@ +package com.pixelized.rplexicon.ui.screens.character.pages.actions + +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.pixelized.rplexicon.model.DiceThrow +import com.pixelized.rplexicon.repository.data.character.DescriptionRepository +import com.pixelized.rplexicon.repository.data.character.ObjectActionRepository +import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument +import com.pixelized.rplexicon.ui.screens.character.composable.actions.ObjectItemUio +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject + +@HiltViewModel +class ObjectsViewModel @Inject constructor( + private val objectsRepository: ObjectActionRepository, + private val descriptionRepository: DescriptionRepository, + savedStateHandle: SavedStateHandle, +) : ViewModel() { + val character = savedStateHandle.characterSheetArgument.name + + private val _objects = mutableStateOf>(emptyList()) + val objects: State> get() = _objects + + init { + viewModelScope.launch(Dispatchers.IO) { + objectsRepository.data.collect { objects -> + val data = objects[character]?.map { + ObjectItemUio( + name = it.name, + original = descriptionRepository.find(name = it.name)?.original + ) + } ?: emptyList() + + withContext(Dispatchers.Main) { + _objects.value = data + } + } + } + } + + fun onObject(name: String) { + + } + + fun onUse(name: String): DiceThrow? { + val item = objectsRepository.find(character = character, item = name) + return item?.let { + DiceThrow.Object( + character = character, + item = it.name, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/AlterationFactory.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/AlterationFactory.kt index b3f646f..7a94816 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/AlterationFactory.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/AlterationFactory.kt @@ -78,6 +78,8 @@ class AlterationFactory @Inject constructor( listOf(Property.PHYSICAL_RANGE_ATTACK) + (action?.hit?.modifier ?: emptyList()) } + is DiceThrow.Object -> emptyList() + is DiceThrow.PhysicalRangeDamage -> { val action = actionRepository.find(diceThrow.character, action = diceThrow.weapon) listOf(Property.PHYSICAL_RANGE_DAMAGE) + (action?.damage?.modifier ?: emptyList()) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/DiceFactory.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/DiceFactory.kt index bf40fb0..b87fbf4 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/DiceFactory.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/factory/DiceFactory.kt @@ -3,6 +3,7 @@ package com.pixelized.rplexicon.ui.screens.rolls.factory import com.pixelized.rplexicon.R import com.pixelized.rplexicon.model.DiceThrow import com.pixelized.rplexicon.repository.data.character.ActionRepository +import com.pixelized.rplexicon.repository.data.character.ObjectActionRepository import com.pixelized.rplexicon.repository.data.character.SkillRepository import com.pixelized.rplexicon.repository.data.character.SpellRepository import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio @@ -11,6 +12,7 @@ import javax.inject.Inject class DiceFactory @Inject constructor( private val actionRepository: ActionRepository, + private val objectActionRepository: ObjectActionRepository, private val skillRepository: SkillRepository, private val spellRepository: SpellRepository, ) { @@ -50,6 +52,13 @@ class DiceFactory @Inject constructor( RollDiceUio(icon = it) } + is DiceThrow.Object -> objectActionRepository.find( + character = diceThrow.character, + item = diceThrow.item, + )?.effect?.faces?.icon?.let { + RollDiceUio(icon = it) + } + else -> RollDiceUio(icon = R.drawable.ic_d20_24) } } \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 7547dbb..3db528f 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -11,6 +11,7 @@ La structure de la feuille de description semble avoir changé et n\'est plus compatible avec cette application. La structure de la feuille d\'équipement semble avoir changé et n\'est plus compatible avec cette application. La structure de la feuille d\'inventaire semble avoir changé et n\'est plus compatible avec cette application. + La structure de la feuille d\'actions (object) semble avoir changé et n\'est plus compatible avec cette application. La structure de la feuille des talents semble avoir changé et n\'est plus compatible avec cette application. La structure de la feuille des sorts semble avoir changé et n\'est plus compatible avec cette application. La structure du lexique semble avoir changé et n\'est plus compatible avec cette application. @@ -99,6 +100,7 @@ Maîtrises Capacités Attaques + Objets Inventaire Equipement Force diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bd1db62..1bdf2e6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,7 @@ The description sheet structure appears to have changed and is no longer compatible with this application The equipment sheet structure appears to have changed and is no longer compatible with this application The inventory sheet structure appears to have changed and is no longer compatible with this application + The objects sheet structure appears to have changed and is no longer compatible with this application The skill sheet structure appears to have changed and is no longer compatible with this application The spell sheet structure appears to have changed and is no longer compatible with this application The lexicon sheet structure appears to have changed and is no longer compatible with this application @@ -98,6 +99,7 @@ Saving Throws Proficiencies Attacks + Objects Skills Inventory Equipment