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.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.model.Description
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.DescriptionRepository
import com.pixelized.rplexicon.repository.data.LexiconRepository
import com.pixelized.rplexicon.repository.data.LocationRepository
import com.pixelized.rplexicon.repository.data.QuestRepository
@ -29,6 +31,7 @@ class LauncherViewModel @Inject constructor(
characterSheetRepository: CharacterSheetRepository,
actionRepository: ActionRepository,
spellRepository: SpellRepository,
descriptionRepository: DescriptionRepository,
) : ViewModel() {
private val _error = MutableSharedFlow<String>()
@ -67,7 +70,14 @@ class LauncherViewModel @Inject constructor(
_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 {
try {

View file

@ -5,7 +5,6 @@ data class Alteration(
val source: String,
val target: String,
val active: Boolean = false,
val description: String,
val status: Map<Property, 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 requirement: String,
val duration: String,
val description: String,
val ritual: Boolean,
) {
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
import com.pixelized.rplexicon.model.Description
object Sheet {
object Lexicon {
const val ID = "1oL9Nu5y37BPEbKxHre4TN9o8nrgy2JQoON4RRkdAHMs"
@ -17,7 +19,7 @@ object Sheet {
const val ATTACK = "Attaques"
const val MAGIC = "Magies"
const val MAGIC_LEXICON = "Lexique magique"
const val STATUS = "État des personnages"
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,
target = target,
active = false,
description = row.parseString(DESCRIPTION) ?: "",
status = properties
.mapNotNull { property ->
val column = alterationStructure.getValue(property.key)
@ -128,8 +127,7 @@ class AlterationParser @Inject constructor(
private const val TARGET = "Cible"
private const val SOURCE = "Source"
private const val DESCRIPTION = "Description"
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 requirement = row.parse(REQUIREMENT)
val duration = row.parse(DURATION)
val description = row.parse(DESCRIPTION)
val ritual = row.parse(RITUAL)?.toBoolean() ?: false
if (name != null
&& level != null
@ -47,7 +46,6 @@ class SpellBookParser @Inject constructor() {
&& range != null
&& requirement != null
&& duration != null
&& description != null
) {
Spell(
name = name,
@ -58,7 +56,6 @@ class SpellBookParser @Inject constructor() {
range = range,
requirement = requirement,
duration = duration,
description = description,
ritual = ritual,
)
} else {
@ -88,7 +85,6 @@ class SpellBookParser @Inject constructor() {
private const val REQUIREMENT = "Composantes"
private const val DURATION = "Durée"
private const val RITUAL = "Rituel"
private const val DESCRIPTION = "Description"
private val COLUMNS
get() = listOf(
@ -101,7 +97,6 @@ class SpellBookParser @Inject constructor() {
REQUIREMENT,
DURATION,
RITUAL,
DESCRIPTION,
)
}
}

View file

@ -9,7 +9,7 @@ import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalContext
import com.pixelized.rplexicon.LocalSnack
import com.pixelized.rplexicon.R
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.Flow
@Stable
sealed class FetchErrorUio {
@ -25,7 +25,7 @@ sealed class FetchErrorUio {
@Composable
fun HandleFetchError(
errors: SharedFlow<FetchErrorUio>,
errors: Flow<FetchErrorUio>,
onStructureError: suspend (context: Context, snack: SnackbarHostState, error: FetchErrorUio.Structure) -> Unit = { context, snack, _ ->
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.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
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.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
@ -53,7 +51,6 @@ import com.pixelized.rplexicon.LocalSnack
import com.pixelized.rplexicon.NO_WINDOW_INSETS
import com.pixelized.rplexicon.R
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.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio.ID.*
@ -112,6 +109,7 @@ fun CharacterSheetScreen(
pagerState = pagerState,
sheetState = sheetState,
refreshState = refresh,
name = viewModel.character,
onRefresh = {
scope.launch { viewModel.update(force = true) }
},
@ -120,6 +118,9 @@ fun CharacterSheetScreen(
},
indicator = {
IndicatorStep(
modifier = Modifier
.align(alignment = Alignment.BottomCenter)
.padding(all = 4.dp),
count = pagerState.pageCount,
selectedIndex = pagerState.currentPage,
)
@ -191,12 +192,13 @@ private fun CharacterSheetContent(
sheetState: ModalBottomSheetState,
refreshState: PullRefreshState,
onRefresh: () -> Unit,
name: String,
onBack: () -> Unit,
loader: @Composable BoxScope.() -> Unit,
proficiencies: @Composable PagerScope.() -> Unit,
actions: @Composable PagerScope.() -> Unit,
alterations: @Composable PagerScope.() -> Unit,
indicator: @Composable ColumnScope.() -> Unit,
indicator: @Composable BoxScope.() -> Unit,
sheet: @Composable () -> Unit,
) {
Scaffold(
@ -223,9 +225,7 @@ private fun CharacterSheetContent(
}
},
title = {
Text(
text = stringResource(id = R.string.character_sheet_title),
)
Text(text = name)
},
)
},
@ -266,8 +266,8 @@ private fun CharacterSheetContent(
}
)
loader()
indicator()
}
indicator()
}
}
)
@ -296,13 +296,22 @@ private fun CharacterScreenPreview(
sheetState = sheetState,
pagerState = rememberPagerState(initialPage = preview) { 2 },
refreshState = rememberPullRefreshState(refreshing = false, onRefresh = { }),
onRefresh = { },
name = "Brulkhai",
onBack = { },
onRefresh = { },
loader = { },
proficiencies = { ProficiencyPreview() },
actions = { ActionPagePreview() },
alterations = { AlterationPagePreview() },
indicator = { IndicatorStepPreview() },
indicator = {
IndicatorStep(
modifier = Modifier
.align(alignment = Alignment.BottomCenter)
.padding(all = 4.dp),
count = 3,
selectedIndex = 0,
)
},
sheet = { SpellLevelChooserPreview() },
)
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,17 +1,19 @@
package com.pixelized.rplexicon.ui.screens.character.pages.alteration
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.ViewModel
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.CharacterSheetRepository
import com.pixelized.rplexicon.repository.data.DescriptionRepository
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.RollAlterationUio
import com.pixelized.rplexicon.ui.screens.rolls.factory.AlterationFactory
import com.pixelized.rplexicon.utilitary.extentions.context
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -21,9 +23,11 @@ import javax.inject.Inject
@HiltViewModel
class AlterationViewModel @Inject constructor(
private val alterationRepository: AlterationRepository,
private val descriptionRepository: DescriptionRepository,
private val factory: AlterationFactory,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
application: Application
) : AndroidViewModel(application) {
private val character = savedStateHandle.characterSheetArgument.name
private val _alterations = mutableStateOf<List<RollAlterationUio>>(emptyList())
@ -56,12 +60,16 @@ class AlterationViewModel @Inject constructor(
fun showAlterationDetail(id: String) {
val alteration = alterationRepository.getAlterations(character = character)
.firstOrNull { it.name == id }
val description = descriptionRepository.find(name = alteration?.name)
if (alteration != null) {
_alterationDetail.value = AlterationDetailUio(
name = id,
original = description?.original,
source = alteration.source,
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.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.business.DiceThrowUseCase
import com.pixelized.rplexicon.model.DiceThrow
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.RollAlterationUio
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.factory.AlterationFactory
import com.pixelized.rplexicon.ui.screens.rolls.factory.DiceFactory
import com.pixelized.rplexicon.utilitary.extentions.context
import com.pixelized.rplexicon.utilitary.extentions.switch
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
@ -24,6 +27,7 @@ import javax.inject.Inject
@HiltViewModel
class RollOverlayViewModel @Inject constructor(
private val alterationRepository: AlterationRepository,
private val descriptionRepository: DescriptionRepository,
private val rollUseCase: DiceThrowUseCase,
private val diceFactory: DiceFactory,
private val alterationFactory: AlterationFactory,
@ -98,12 +102,16 @@ class RollOverlayViewModel @Inject constructor(
val alteration = diceThrow?.character?.let { character ->
alterationRepository.getAlterations(character = character).firstOrNull { it.name == id }
}
val description = descriptionRepository.find(name = alteration?.name)
if (alteration != null) {
_alterationDetail.value = AlterationDetailUio(
name = id,
original = description?.original,
source = alteration.source,
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 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.fillMaxWidth
import androidx.compose.foundation.layout.padding
@ -25,6 +27,7 @@ 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.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -37,11 +40,13 @@ import com.pixelized.rplexicon.utilitary.extentions.lexicon
@Stable
data class AlterationDetailUio(
val name: String,
val original: String?,
val source: String,
val target: String,
val description: String,
)
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun AlterationDetail(
modifier: Modifier = Modifier,
@ -65,22 +70,38 @@ fun AlterationDetail(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top,
) {
Text(
FlowRow(
modifier = Modifier.padding(top = 16.dp),
style = MaterialTheme.typography.titleMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
text = AnnotatedString(
text = detail.name,
spanStyles = listOf(
AnnotatedString.Range(
item = MaterialTheme.lexicon.typography.bodyDropCapSpan,
start = 0,
end = Integer.min(1, detail.name.length),
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
modifier = Modifier.alignByBaseline(),
style = MaterialTheme.typography.titleMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
text = AnnotatedString(
text = detail.name,
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) {
Icon(
imageVector = Icons.Default.Close,
@ -123,6 +144,7 @@ private fun AlterationDetailPreview() {
AlterationDetail(
detail = AlterationDetailUio(
name = "Rage",
original = "Rage",
source = "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" +

View file

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

View file

@ -155,6 +155,8 @@
<string name="alteration_source">Source : %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_rage">Rage</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_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_rage">Rage</string>
<string name="token_label_relentless_endurance">Relentless Endurance</string>