Add a specific tab for description.

This commit is contained in:
Thomas Andres Gomez 2023-10-09 09:24:28 +02:00
parent d8f79b92f9
commit 6528b89f6b
23 changed files with 242 additions and 138 deletions

View file

@ -5,9 +5,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.model.Description
import com.pixelized.rplexicon.repository.data.ActionRepository import com.pixelized.rplexicon.repository.data.ActionRepository
import com.pixelized.rplexicon.repository.data.AlterationRepository import com.pixelized.rplexicon.repository.data.AlterationRepository
import com.pixelized.rplexicon.repository.data.CharacterSheetRepository import com.pixelized.rplexicon.repository.data.CharacterSheetRepository
import com.pixelized.rplexicon.repository.data.DescriptionRepository
import com.pixelized.rplexicon.repository.data.LexiconRepository import com.pixelized.rplexicon.repository.data.LexiconRepository
import com.pixelized.rplexicon.repository.data.LocationRepository import com.pixelized.rplexicon.repository.data.LocationRepository
import com.pixelized.rplexicon.repository.data.QuestRepository import com.pixelized.rplexicon.repository.data.QuestRepository
@ -29,6 +31,7 @@ class LauncherViewModel @Inject constructor(
characterSheetRepository: CharacterSheetRepository, characterSheetRepository: CharacterSheetRepository,
actionRepository: ActionRepository, actionRepository: ActionRepository,
spellRepository: SpellRepository, spellRepository: SpellRepository,
descriptionRepository: DescriptionRepository,
) : ViewModel() { ) : ViewModel() {
private val _error = MutableSharedFlow<String>() private val _error = MutableSharedFlow<String>()
@ -67,7 +70,14 @@ class LauncherViewModel @Inject constructor(
_error.tryEmit("CharacterSheet fail to update") _error.tryEmit("CharacterSheet fail to update")
} }
} }
awaitAll(lexicon, location, quest, characterSheet) val descriptionSheet = async {
try {
descriptionRepository.fetchDescription()
} catch (exception: Exception) {
_error.tryEmit("Skill/Spell description fail to update")
}
}
awaitAll(lexicon, location, quest, characterSheet, descriptionSheet)
val alteration = async { val alteration = async {
try { try {

View file

@ -5,7 +5,6 @@ data class Alteration(
val source: String, val source: String,
val target: String, val target: String,
val active: Boolean = false, val active: Boolean = false,
val description: String,
val status: Map<Property, Status>, val status: Map<Property, Status>,
) { ) {
data class Status( data class Status(

View file

@ -1,7 +0,0 @@
package com.pixelized.rplexicon.model
data class Counter(
val title: String? = null,
val value: Int,
val max: Int?,
)

View file

@ -0,0 +1,7 @@
package com.pixelized.rplexicon.model
data class Description(
val name: String,
val original: String,
val description: String,
)

View file

@ -9,7 +9,6 @@ data class Spell(
val range: String, val range: String,
val requirement: String, val requirement: String,
val duration: String, val duration: String,
val description: String,
val ritual: Boolean, val ritual: Boolean,
) { ) {
enum class School(val key: String) { enum class School(val key: String) {

View file

@ -0,0 +1,36 @@
package com.pixelized.rplexicon.repository.data
import com.pixelized.rplexicon.model.Description
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
import com.pixelized.rplexicon.repository.parser.DescriptionParser
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 DescriptionRepository @Inject constructor(
private val googleRepository: GoogleSheetServiceRepository,
private val parser: DescriptionParser,
) {
private val _data = MutableStateFlow<Map<String, Description>>(emptyMap())
val data: StateFlow<Map<String, Description>> get() = _data
var lastSuccessFullUpdate: Update = Update.INITIAL
private set
fun find(name: String?): Description? = _data.value[name]
@Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchDescription() {
googleRepository.fetch { sheet ->
val request = sheet.get(Sheet.Character.ID, Sheet.Character.DESCRIPTION)
val data = parser.parse(data = request.execute())
_data.tryEmit(data)
lastSuccessFullUpdate = Update.currentTime()
}
}
}

View file

@ -1,5 +1,7 @@
package com.pixelized.rplexicon.repository.data package com.pixelized.rplexicon.repository.data
import com.pixelized.rplexicon.model.Description
object Sheet { object Sheet {
object Lexicon { object Lexicon {
const val ID = "1oL9Nu5y37BPEbKxHre4TN9o8nrgy2JQoON4RRkdAHMs" const val ID = "1oL9Nu5y37BPEbKxHre4TN9o8nrgy2JQoON4RRkdAHMs"
@ -17,7 +19,7 @@ object Sheet {
const val ATTACK = "Attaques" const val ATTACK = "Attaques"
const val MAGIC = "Magies" const val MAGIC = "Magies"
const val MAGIC_LEXICON = "Lexique magique" const val MAGIC_LEXICON = "Lexique magique"
const val STATUS = "État des personnages"
const val ALTERATION = "Altérations" const val ALTERATION = "Altérations"
const val DESCRIPTION = "Descriptions"
} }
} }

View file

@ -0,0 +1,55 @@
package com.pixelized.rplexicon.repository.parser
import com.google.api.services.sheets.v4.model.ValueRange
import com.pixelized.rplexicon.model.Description
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 DescriptionParser @Inject constructor() {
@Throws(IncompatibleSheetStructure::class)
fun parse(data: ValueRange): Map<String, Description> {
val sheet = data.values.sheet()
val values = hashMapOf<String, Description>()
lateinit var structure: Map<String, Int>
sheet?.forEachIndexed { index, item ->
when {
index == 0 -> {
structure = item.checkSheetStructure(model = COLUMNS)
}
item is List<*> -> {
val name = item[structure.getValue(NAME)] as? String
val translate = item[structure.getValue(TRANSLATE)] as? String
val description = item[structure.getValue(DESCRIPTION)] as? String
if (name != null && translate != null && description != null) {
values[name] = Description(
name = name,
original = translate,
description = description,
)
}
}
}
}
return values
}
companion object {
private const val NAME = "Nom"
private const val TRANSLATE = "Traduction"
private const val DESCRIPTION = "Description"
private val COLUMNS = listOf(
NAME,
TRANSLATE,
DESCRIPTION,
)
}
}

View file

@ -46,7 +46,6 @@ class AlterationParser @Inject constructor(
source = source, source = source,
target = target, target = target,
active = false, active = false,
description = row.parseString(DESCRIPTION) ?: "",
status = properties status = properties
.mapNotNull { property -> .mapNotNull { property ->
val column = alterationStructure.getValue(property.key) val column = alterationStructure.getValue(property.key)
@ -128,8 +127,7 @@ class AlterationParser @Inject constructor(
private const val TARGET = "Cible" private const val TARGET = "Cible"
private const val SOURCE = "Source" private const val SOURCE = "Source"
private const val DESCRIPTION = "Description"
private val COLUMNS private val COLUMNS
get() = listOf(SOURCE, TARGET, DESCRIPTION) + Property.values().map { it.key } get() = listOf(SOURCE, TARGET) + Property.values().map { it.key }
} }
} }

View file

@ -1,60 +0,0 @@
package com.pixelized.rplexicon.repository.parser.alteration
import com.google.api.services.sheets.v4.model.ValueRange
import com.pixelized.rplexicon.model.Counter
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import com.pixelized.rplexicon.utilitary.extentions.sheet
import javax.inject.Inject
class CounterParser @Inject constructor() {
@Throws(IncompatibleSheetStructure::class)
fun parse(values: ValueRange): Map<String, List<Counter>> {
val sheet = values.values.sheet()
lateinit var characters: List<String>
val counters = hashMapOf<String, MutableList<Counter>>()
sheet?.mapNotNull { it as? List<*> }?.forEachIndexed { columnIndex, row ->
when (columnIndex) {
0 -> characters = row
.subList(fromIndex = 1, toIndex = row.size)
.map { it.toString() }
else -> {
row.getOrNull(0)?.toString()?.let { title ->
row.subList(fromIndex = 1, toIndex = row.size)
.forEachIndexed { rowIndex, value ->
val counter = parseCounter(title = title, value = value?.toString())
if (counter != null) {
counters
.getOrPut(characters[rowIndex]) { mutableListOf() }
.add(counter)
}
}
}
}
}
}
return counters
}
fun parseCounter(title: String? = null, value: String?): Counter? {
return if (value != null) {
COUNTER_REGEX.find(value)?.let {
val (actual, max) = it.destructured
Counter(
title = title,
value = actual.toIntOrNull() ?: 0,
max = max.toIntOrNull(),
)
}
} else {
null
}
}
companion object {
val COUNTER_REGEX = Regex("(\\d+)\\/(\\d+)")
}
}

View file

@ -37,7 +37,6 @@ class SpellBookParser @Inject constructor() {
val range = row.parse(RANGE) val range = row.parse(RANGE)
val requirement = row.parse(REQUIREMENT) val requirement = row.parse(REQUIREMENT)
val duration = row.parse(DURATION) val duration = row.parse(DURATION)
val description = row.parse(DESCRIPTION)
val ritual = row.parse(RITUAL)?.toBoolean() ?: false val ritual = row.parse(RITUAL)?.toBoolean() ?: false
if (name != null if (name != null
&& level != null && level != null
@ -47,7 +46,6 @@ class SpellBookParser @Inject constructor() {
&& range != null && range != null
&& requirement != null && requirement != null
&& duration != null && duration != null
&& description != null
) { ) {
Spell( Spell(
name = name, name = name,
@ -58,7 +56,6 @@ class SpellBookParser @Inject constructor() {
range = range, range = range,
requirement = requirement, requirement = requirement,
duration = duration, duration = duration,
description = description,
ritual = ritual, ritual = ritual,
) )
} else { } else {
@ -88,7 +85,6 @@ class SpellBookParser @Inject constructor() {
private const val REQUIREMENT = "Composantes" private const val REQUIREMENT = "Composantes"
private const val DURATION = "Durée" private const val DURATION = "Durée"
private const val RITUAL = "Rituel" private const val RITUAL = "Rituel"
private const val DESCRIPTION = "Description"
private val COLUMNS private val COLUMNS
get() = listOf( get() = listOf(
@ -101,7 +97,6 @@ class SpellBookParser @Inject constructor() {
REQUIREMENT, REQUIREMENT,
DURATION, DURATION,
RITUAL, RITUAL,
DESCRIPTION,
) )
} }
} }

View file

@ -9,7 +9,7 @@ import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import com.pixelized.rplexicon.LocalSnack import com.pixelized.rplexicon.LocalSnack
import com.pixelized.rplexicon.R import com.pixelized.rplexicon.R
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.Flow
@Stable @Stable
sealed class FetchErrorUio { sealed class FetchErrorUio {
@ -25,7 +25,7 @@ sealed class FetchErrorUio {
@Composable @Composable
fun HandleFetchError( fun HandleFetchError(
errors: SharedFlow<FetchErrorUio>, errors: Flow<FetchErrorUio>,
onStructureError: suspend (context: Context, snack: SnackbarHostState, error: FetchErrorUio.Structure) -> Unit = { context, snack, _ -> onStructureError: suspend (context: Context, snack: SnackbarHostState, error: FetchErrorUio.Structure) -> Unit = { context, snack, _ ->
snack.showSnackbar(message = context.getString(R.string.error_structure)) snack.showSnackbar(message = context.getString(R.string.error_structure))
}, },

View file

@ -8,7 +8,6 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
@ -42,7 +41,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource 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.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@ -53,7 +51,6 @@ import com.pixelized.rplexicon.LocalSnack
import com.pixelized.rplexicon.NO_WINDOW_INSETS import com.pixelized.rplexicon.NO_WINDOW_INSETS
import com.pixelized.rplexicon.R import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.composable.IndicatorStep import com.pixelized.rplexicon.ui.composable.IndicatorStep
import com.pixelized.rplexicon.ui.composable.IndicatorStepPreview
import com.pixelized.rplexicon.ui.composable.Loader import com.pixelized.rplexicon.ui.composable.Loader
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio.ID.* import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio.ID.*
@ -112,6 +109,7 @@ fun CharacterSheetScreen(
pagerState = pagerState, pagerState = pagerState,
sheetState = sheetState, sheetState = sheetState,
refreshState = refresh, refreshState = refresh,
name = viewModel.character,
onRefresh = { onRefresh = {
scope.launch { viewModel.update(force = true) } scope.launch { viewModel.update(force = true) }
}, },
@ -120,6 +118,9 @@ fun CharacterSheetScreen(
}, },
indicator = { indicator = {
IndicatorStep( IndicatorStep(
modifier = Modifier
.align(alignment = Alignment.BottomCenter)
.padding(all = 4.dp),
count = pagerState.pageCount, count = pagerState.pageCount,
selectedIndex = pagerState.currentPage, selectedIndex = pagerState.currentPage,
) )
@ -191,12 +192,13 @@ private fun CharacterSheetContent(
sheetState: ModalBottomSheetState, sheetState: ModalBottomSheetState,
refreshState: PullRefreshState, refreshState: PullRefreshState,
onRefresh: () -> Unit, onRefresh: () -> Unit,
name: String,
onBack: () -> Unit, onBack: () -> Unit,
loader: @Composable BoxScope.() -> Unit, loader: @Composable BoxScope.() -> Unit,
proficiencies: @Composable PagerScope.() -> Unit, proficiencies: @Composable PagerScope.() -> Unit,
actions: @Composable PagerScope.() -> Unit, actions: @Composable PagerScope.() -> Unit,
alterations: @Composable PagerScope.() -> Unit, alterations: @Composable PagerScope.() -> Unit,
indicator: @Composable ColumnScope.() -> Unit, indicator: @Composable BoxScope.() -> Unit,
sheet: @Composable () -> Unit, sheet: @Composable () -> Unit,
) { ) {
Scaffold( Scaffold(
@ -223,9 +225,7 @@ private fun CharacterSheetContent(
} }
}, },
title = { title = {
Text( Text(text = name)
text = stringResource(id = R.string.character_sheet_title),
)
}, },
) )
}, },
@ -266,8 +266,8 @@ private fun CharacterSheetContent(
} }
) )
loader() loader()
indicator()
} }
indicator()
} }
} }
) )
@ -296,13 +296,22 @@ private fun CharacterScreenPreview(
sheetState = sheetState, sheetState = sheetState,
pagerState = rememberPagerState(initialPage = preview) { 2 }, pagerState = rememberPagerState(initialPage = preview) { 2 },
refreshState = rememberPullRefreshState(refreshing = false, onRefresh = { }), refreshState = rememberPullRefreshState(refreshing = false, onRefresh = { }),
onRefresh = { }, name = "Brulkhai",
onBack = { }, onBack = { },
onRefresh = { },
loader = { }, loader = { },
proficiencies = { ProficiencyPreview() }, proficiencies = { ProficiencyPreview() },
actions = { ActionPagePreview() }, actions = { ActionPagePreview() },
alterations = { AlterationPagePreview() }, alterations = { AlterationPagePreview() },
indicator = { IndicatorStepPreview() }, indicator = {
IndicatorStep(
modifier = Modifier
.align(alignment = Alignment.BottomCenter)
.padding(all = 4.dp),
count = 3,
selectedIndex = 0,
)
},
sheet = { SpellLevelChooserPreview() }, sheet = { SpellLevelChooserPreview() },
) )
} }

View file

@ -3,6 +3,7 @@ package com.pixelized.rplexicon.ui.screens.character
import android.util.Log import android.util.Log
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.repository.authentication.FirebaseRepository import com.pixelized.rplexicon.repository.authentication.FirebaseRepository
@ -11,6 +12,7 @@ import com.pixelized.rplexicon.repository.data.AlterationRepository
import com.pixelized.rplexicon.repository.data.CharacterSheetRepository import com.pixelized.rplexicon.repository.data.CharacterSheetRepository
import com.pixelized.rplexicon.repository.data.SpellRepository import com.pixelized.rplexicon.repository.data.SpellRepository
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
@ -27,6 +29,7 @@ class CharacterSheetViewModel @Inject constructor(
private val actionRepository: ActionRepository, private val actionRepository: ActionRepository,
private val spellRepository: SpellRepository, private val spellRepository: SpellRepository,
private val firebaseRepository: FirebaseRepository, private val firebaseRepository: FirebaseRepository,
savedStateHandle: SavedStateHandle,
) : ViewModel() { ) : ViewModel() {
private val _isLoading = mutableStateOf(false) private val _isLoading = mutableStateOf(false)
val isLoading: State<Boolean> get() = _isLoading val isLoading: State<Boolean> get() = _isLoading
@ -34,6 +37,8 @@ class CharacterSheetViewModel @Inject constructor(
private val _error = MutableSharedFlow<FetchErrorUio.Firebase>() private val _error = MutableSharedFlow<FetchErrorUio.Firebase>()
val errors: SharedFlow<FetchErrorUio.Firebase> get() = _error val errors: SharedFlow<FetchErrorUio.Firebase> get() = _error
val character = savedStateHandle.characterSheetArgument.name
init { init {
viewModelScope.launch { viewModelScope.launch {
launch { launch {

View file

@ -149,7 +149,7 @@ fun ActionsPageContent(
LazyColumn( LazyColumn(
modifier = modifier, modifier = modifier,
state = lazyListState, state = lazyListState,
contentPadding = PaddingValues(bottom = 8.dp), contentPadding = PaddingValues(bottom = 16.dp),
) { ) {
stickyHeader { stickyHeader {
CharacterSheetHeader( CharacterSheetHeader(

View file

@ -45,8 +45,8 @@ class SpellsActionViewModel @Inject constructor(
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
) : AndroidViewModel(application) { ) : AndroidViewModel(application) {
private val characterName = savedStateHandle.characterSheetArgument.name private val characterName = savedStateHandle.characterSheetArgument.name
private val character: CharacterSheet private var character: CharacterSheet? = null
get() = characterRepository.data.value.getValue(characterName) private var characterFire: CharacterSheetFire? = null
private val _spells = mutableStateOf<List<Pair<SpellHeaderUio, List<SpellUio>>>>(emptyList()) private val _spells = mutableStateOf<List<Pair<SpellHeaderUio, List<SpellUio>>>>(emptyList())
val spells: State<List<Pair<SpellHeaderUio, List<SpellUio>>>> get() = _spells val spells: State<List<Pair<SpellHeaderUio, List<SpellUio>>>> get() = _spells
@ -60,9 +60,11 @@ class SpellsActionViewModel @Inject constructor(
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
characterRepository.data characterRepository.data
.combine(spellRepository.spells) { sheets, spells -> .combine(spellRepository.spells) { sheets, spells ->
character = sheets.getValue(characterName)
Struct(sheets = sheets, spells = spells) Struct(sheets = sheets, spells = spells)
} }
.combine(firebaseRepository.getCharacter(character = characterName)) { struct, fire -> .combine(firebaseRepository.getCharacter(character = characterName)) { struct, fire ->
characterFire = fire
struct.also { it.fire = fire } struct.also { it.fire = fire }
} }
.collect { data -> .collect { data ->
@ -136,18 +138,20 @@ class SpellsActionViewModel @Inject constructor(
character = characterName, character = characterName,
spell = name, spell = name,
) )
return when (character.isWarlock) { return when (character?.isWarlock?: false) {
true -> false true -> false
else -> character.highestSpellLevel() > (assignedSpell?.spell?.level ?: 1) else -> (character?.highestSpellLevel() ?: 1) > (assignedSpell?.spell?.level ?: 1)
} }
} }
fun prepareSpellCast(name: String) { fun prepareSpellCast(name: String) {
val character = character
val characterFire = characterFire
val assignedSpell = spellRepository.find( val assignedSpell = spellRepository.find(
character = characterName, character = characterName,
spell = name, spell = name,
) )
if (assignedSpell != null) { if (assignedSpell != null && character != null && characterFire != null) {
val icon = assignedSpell.effect?.faces?.icon ?: R.drawable.ic_d20_24 val icon = assignedSpell.effect?.faces?.icon ?: R.drawable.ic_d20_24
val base = assignedSpell.effect?.toString(character = character, level = 1) ?: "" val base = assignedSpell.effect?.toString(character = character, level = 1) ?: ""
_preparedSpellLevel.value = SpellChooserUio( _preparedSpellLevel.value = SpellChooserUio(
@ -156,7 +160,7 @@ class SpellsActionViewModel @Inject constructor(
size = max(0, character.highestSpellLevel() + 1 - assignedSpell.spell.level) size = max(0, character.highestSpellLevel() + 1 - assignedSpell.spell.level)
) { index -> ) { index ->
val level = assignedSpell.spell.level + index val level = assignedSpell.spell.level + index
val remaining = character.spell(level) val remaining = characterFire.spell(level)
val max = character.spell(level) val max = character.spell(level)
SpellLevelUio( SpellLevelUio(
@ -179,8 +183,8 @@ class SpellsActionViewModel @Inject constructor(
val spell = spellRepository.find(character = characterName, spell = id) val spell = spellRepository.find(character = characterName, spell = id)
return onCastSpell( return onCastSpell(
id = id, id = id,
level = when (character.isWarlock) { level = when (character?.isWarlock ?: false) {
true -> character.firstSpellSlot() ?: 1 true -> character?.firstSpellSlot() ?: 1
else -> spell?.spell?.level ?: 1 else -> spell?.spell?.level ?: 1
}, },
) )

View file

@ -59,7 +59,7 @@ fun AlterationPageContent(
) { ) {
LazyColumn( LazyColumn(
modifier = modifier, modifier = modifier,
contentPadding = PaddingValues(vertical = 8.dp), contentPadding = PaddingValues(top = 8.dp, bottom = 16.dp),
) { ) {
items(items = alterations.value) { items(items = alterations.value) {
RollAlteration( RollAlteration(

View file

@ -1,17 +1,19 @@
package com.pixelized.rplexicon.ui.screens.character.pages.alteration package com.pixelized.rplexicon.ui.screens.character.pages.alteration
import android.app.Application
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.repository.authentication.FirebaseRepository import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.repository.data.AlterationRepository import com.pixelized.rplexicon.repository.data.AlterationRepository
import com.pixelized.rplexicon.repository.data.CharacterSheetRepository import com.pixelized.rplexicon.repository.data.DescriptionRepository
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
import com.pixelized.rplexicon.ui.screens.rolls.composable.AlterationDetailUio import com.pixelized.rplexicon.ui.screens.rolls.composable.AlterationDetailUio
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio
import com.pixelized.rplexicon.ui.screens.rolls.factory.AlterationFactory import com.pixelized.rplexicon.ui.screens.rolls.factory.AlterationFactory
import com.pixelized.rplexicon.utilitary.extentions.context
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -21,9 +23,11 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class AlterationViewModel @Inject constructor( class AlterationViewModel @Inject constructor(
private val alterationRepository: AlterationRepository, private val alterationRepository: AlterationRepository,
private val descriptionRepository: DescriptionRepository,
private val factory: AlterationFactory, private val factory: AlterationFactory,
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
) : ViewModel() { application: Application
) : AndroidViewModel(application) {
private val character = savedStateHandle.characterSheetArgument.name private val character = savedStateHandle.characterSheetArgument.name
private val _alterations = mutableStateOf<List<RollAlterationUio>>(emptyList()) private val _alterations = mutableStateOf<List<RollAlterationUio>>(emptyList())
@ -56,12 +60,16 @@ class AlterationViewModel @Inject constructor(
fun showAlterationDetail(id: String) { fun showAlterationDetail(id: String) {
val alteration = alterationRepository.getAlterations(character = character) val alteration = alterationRepository.getAlterations(character = character)
.firstOrNull { it.name == id } .firstOrNull { it.name == id }
val description = descriptionRepository.find(name = alteration?.name)
if (alteration != null) { if (alteration != null) {
_alterationDetail.value = AlterationDetailUio( _alterationDetail.value = AlterationDetailUio(
name = id, name = id,
original = description?.original,
source = alteration.source, source = alteration.source,
target = alteration.target, target = alteration.target,
description = alteration.description description = description?.description
?: context.getString(R.string.no_available_description)
) )
} }
} }

View file

@ -5,15 +5,18 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.business.DiceThrowUseCase import com.pixelized.rplexicon.business.DiceThrowUseCase
import com.pixelized.rplexicon.model.DiceThrow import com.pixelized.rplexicon.model.DiceThrow
import com.pixelized.rplexicon.repository.data.AlterationRepository import com.pixelized.rplexicon.repository.data.AlterationRepository
import com.pixelized.rplexicon.repository.data.DescriptionRepository
import com.pixelized.rplexicon.ui.screens.rolls.composable.AlterationDetailUio import com.pixelized.rplexicon.ui.screens.rolls.composable.AlterationDetailUio
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio import com.pixelized.rplexicon.ui.screens.rolls.composable.RollAlterationUio
import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio import com.pixelized.rplexicon.ui.screens.rolls.composable.RollDiceUio
import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio import com.pixelized.rplexicon.ui.screens.rolls.composable.ThrowsCardUio
import com.pixelized.rplexicon.ui.screens.rolls.factory.AlterationFactory import com.pixelized.rplexicon.ui.screens.rolls.factory.AlterationFactory
import com.pixelized.rplexicon.ui.screens.rolls.factory.DiceFactory import com.pixelized.rplexicon.ui.screens.rolls.factory.DiceFactory
import com.pixelized.rplexicon.utilitary.extentions.context
import com.pixelized.rplexicon.utilitary.extentions.switch import com.pixelized.rplexicon.utilitary.extentions.switch
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -24,6 +27,7 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class RollOverlayViewModel @Inject constructor( class RollOverlayViewModel @Inject constructor(
private val alterationRepository: AlterationRepository, private val alterationRepository: AlterationRepository,
private val descriptionRepository: DescriptionRepository,
private val rollUseCase: DiceThrowUseCase, private val rollUseCase: DiceThrowUseCase,
private val diceFactory: DiceFactory, private val diceFactory: DiceFactory,
private val alterationFactory: AlterationFactory, private val alterationFactory: AlterationFactory,
@ -98,12 +102,16 @@ class RollOverlayViewModel @Inject constructor(
val alteration = diceThrow?.character?.let { character -> val alteration = diceThrow?.character?.let { character ->
alterationRepository.getAlterations(character = character).firstOrNull { it.name == id } alterationRepository.getAlterations(character = character).firstOrNull { it.name == id }
} }
val description = descriptionRepository.find(name = alteration?.name)
if (alteration != null) { if (alteration != null) {
_alterationDetail.value = AlterationDetailUio( _alterationDetail.value = AlterationDetailUio(
name = id, name = id,
original = description?.original,
source = alteration.source, source = alteration.source,
target = alteration.target, target = alteration.target,
description = alteration.description description = description?.description
?: context.getString(R.string.no_available_description)
) )
} }
} }

View file

@ -4,6 +4,8 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column 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.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -25,6 +27,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -37,11 +40,13 @@ import com.pixelized.rplexicon.utilitary.extentions.lexicon
@Stable @Stable
data class AlterationDetailUio( data class AlterationDetailUio(
val name: String, val name: String,
val original: String?,
val source: String, val source: String,
val target: String, val target: String,
val description: String, val description: String,
) )
@OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun AlterationDetail( fun AlterationDetail(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -65,22 +70,38 @@ fun AlterationDetail(
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top, verticalAlignment = Alignment.Top,
) { ) {
Text( FlowRow(
modifier = Modifier.padding(top = 16.dp), modifier = Modifier.padding(top = 16.dp),
style = MaterialTheme.typography.titleMedium, horizontalArrangement = Arrangement.spacedBy(4.dp),
maxLines = 1, ) {
overflow = TextOverflow.Ellipsis, Text(
text = AnnotatedString( modifier = Modifier.alignByBaseline(),
text = detail.name, style = MaterialTheme.typography.titleMedium,
spanStyles = listOf( maxLines = 1,
AnnotatedString.Range( overflow = TextOverflow.Ellipsis,
item = MaterialTheme.lexicon.typography.bodyDropCapSpan, text = AnnotatedString(
start = 0, text = detail.name,
end = Integer.min(1, detail.name.length), spanStyles = listOf(
AnnotatedString.Range(
item = MaterialTheme.lexicon.typography.bodyDropCapSpan,
start = 0,
end = Integer.min(1, detail.name.length),
)
) )
),
)
detail.original?.let {
Text(
modifier = Modifier.alignByBaseline(),
fontWeight = FontWeight.Light,
fontStyle = FontStyle.Italic,
style = MaterialTheme.typography.labelSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
text = it,
) )
), }
) }
IconButton(onClick = onClose) { IconButton(onClick = onClose) {
Icon( Icon(
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
@ -123,6 +144,7 @@ private fun AlterationDetailPreview() {
AlterationDetail( AlterationDetail(
detail = AlterationDetailUio( detail = AlterationDetailUio(
name = "Rage", name = "Rage",
original = "Rage",
source = "Barbare", source = "Barbare",
target = "Barbare", target = "Barbare",
description = "\"En combat, vous vous battez avec une férocité bestiale. Durant votre tour, vous pouvez entrer en rage en utilisant une action bonus. En rage, vous gagnez les bénéfices suivants si vous ne portez pas d'armure lourde :\n" + description = "\"En combat, vous vous battez avec une férocité bestiale. Durant votre tour, vous pouvez entrer en rage en utilisant une action bonus. En rage, vous gagnez les bénéfices suivants si vous ne portez pas d'armure lourde :\n" +

View file

@ -5,48 +5,58 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.repository.data.DescriptionRepository
import com.pixelized.rplexicon.repository.data.SpellRepository import com.pixelized.rplexicon.repository.data.SpellRepository
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
import com.pixelized.rplexicon.ui.navigation.screens.spellDetailArgument import com.pixelized.rplexicon.ui.navigation.screens.spellDetailArgument
import com.pixelized.rplexicon.utilitary.extentions.local.icon import com.pixelized.rplexicon.utilitary.extentions.local.icon
import com.pixelized.rplexicon.utilitary.extentions.local.label import com.pixelized.rplexicon.utilitary.extentions.local.label
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class SpellDetailViewModel @Inject constructor( class SpellDetailViewModel @Inject constructor(
spellRepository: SpellRepository,
descriptionRepository: DescriptionRepository,
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
repository: SpellRepository
) : ViewModel() { ) : ViewModel() {
val spell: State<SpellDetailUio?> val spell: State<SpellDetailUio?>
val error: SharedFlow<FetchErrorUio> val error: Flow<FetchErrorUio>
init { init {
val argument = savedStateHandle.spellDetailArgument val argument = savedStateHandle.spellDetailArgument
val assignedSpell = repository.find( val description = descriptionRepository.find(
name = argument.spell,
)
val spell = spellRepository.find(
character = argument.character, character = argument.character,
spell = argument.spell, spell = argument.spell,
)?.let { )
val assignedSpell = if (spell != null && description != null) {
SpellDetailUio( SpellDetailUio(
icon = it.spell.school.icon, icon = spell.spell.school.icon,
name = it.spell.name, name = spell.spell.name,
translated = it.spell.originalName, translated = description.original,
level = "${it.spell.level}", level = "${spell.spell.level}",
school = it.spell.school.label, school = spell.spell.school.label,
castingTime = it.spell.castingTime, castingTime = spell.spell.castingTime,
range = it.spell.range, range = spell.spell.range,
requirement = it.spell.requirement, requirement = spell.spell.requirement,
duration = it.spell.duration, duration = spell.spell.duration,
description = it.spell.description, description = description.description,
ritual = it.spell.ritual, ritual = spell.spell.ritual,
) )
} else {
null
} }
spell = mutableStateOf(assignedSpell) this.spell = mutableStateOf(assignedSpell)
error = MutableSharedFlow<FetchErrorUio>().also { flow ->
this.error = MutableSharedFlow<FetchErrorUio>().also { flow ->
if (assignedSpell == null) { if (assignedSpell == null) {
viewModelScope.launch { viewModelScope.launch {
flow.tryEmit(FetchErrorUio.Default) flow.tryEmit(FetchErrorUio.Default)

View file

@ -155,6 +155,8 @@
<string name="alteration_source">Source : %1$s</string> <string name="alteration_source">Source : %1$s</string>
<string name="alteration_target">Cible : %1$s</string> <string name="alteration_target">Cible : %1$s</string>
<string name="no_available_description">Aucune description disponnible</string>
<string name="token_label_title">Capacité</string> <string name="token_label_title">Capacité</string>
<string name="token_label_rage">Rage</string> <string name="token_label_rage">Rage</string>
<string name="token_label_relentless_endurance">Endurance Implacable</string> <string name="token_label_relentless_endurance">Endurance Implacable</string>

View file

@ -155,6 +155,8 @@
<string name="alteration_source">Source: %1$s</string> <string name="alteration_source">Source: %1$s</string>
<string name="alteration_target">Target: %1$s</string> <string name="alteration_target">Target: %1$s</string>
<string name="no_available_description">No available description</string>
<string name="token_label_title">Skill</string> <string name="token_label_title">Skill</string>
<string name="token_label_rage">Rage</string> <string name="token_label_rage">Rage</string>
<string name="token_label_relentless_endurance">Relentless Endurance</string> <string name="token_label_relentless_endurance">Relentless Endurance</string>