Category management - lexicon

This commit is contained in:
Thomas Andres Gomez 2023-11-15 14:16:26 +01:00
parent 020af02c29
commit fa7fcbeae6
24 changed files with 266 additions and 699 deletions

View file

@ -7,39 +7,14 @@ import androidx.compose.runtime.Stable
data class Lexicon(
val id: String,
val name: String,
val category: String?,
val diminutive: String?,
val gender: Gender,
val race: Race,
val gender: String?,
val race: String?,
val status: String?,
val location: String?,
val portrait: List<Uri>,
val description: String?,
val history: String?,
val tags: String?,
) {
@Stable
enum class Gender {
MALE,
FEMALE,
UNDETERMINED,
}
@Stable
enum class Race {
ELF,
HALFLING,
HUMAN,
DWARF,
HALF_ELF,
HALF_ORC,
DRAGONBORN,
GNOME,
TIEFLING,
AARAKOCRA,
GENASI,
DEEP_GNOME,
GOLIATH,
UNDETERMINED,
}
}
)

View file

@ -6,7 +6,7 @@ import androidx.compose.runtime.Stable
@Stable
data class Quest(
val id: String,
val group: String?,
val category: String?,
val title: String,
val entries: List<QuestEntry>,
) {

View file

@ -1,20 +0,0 @@
package com.pixelized.rplexicon.data.parser
import com.pixelized.rplexicon.data.model.Lexicon
import javax.inject.Inject
class GenderParser @Inject constructor() {
fun parse(gender: String?): Lexicon.Gender {
return when (gender?.takeIf { it.isNotBlank() }) {
Gender.MALE -> Lexicon.Gender.MALE
Gender.FEMALE -> Lexicon.Gender.FEMALE
else -> Lexicon.Gender.UNDETERMINED
}
}
private object Gender {
const val MALE = "Male"
const val FEMALE = "Femelle"
}
}

View file

@ -7,8 +7,6 @@ import javax.inject.Inject
class LexiconParser @Inject constructor(
private val portraitParser: PortraitParser,
private val genderParser: GenderParser,
private val raceParser: RaceParser,
) {
@Throws(IncompatibleSheetStructure::class)
fun parse(sheet: ValueRange): List<Lexicon> = parserScope {
@ -26,9 +24,10 @@ class LexiconParser @Inject constructor(
val lexicon = Lexicon(
id = "$name-${ids[name]}",
name = name,
category = row.parse(column = CATEGORY),
diminutive = row.parse(column = SHORT),
gender = genderParser.parse(row.parse(column = GENDER)),
race = raceParser.parser(row.parse(column = RACE)),
gender = row.parse(column = GENDER),
race = row.parse(column = RACE),
status = row.parse(column = STATUS),
location = row.parse(column = LOCATION),
portrait = portraitParser.parse(row.parse(column = PORTRAIT)),
@ -47,6 +46,7 @@ class LexiconParser @Inject constructor(
companion object {
private val NAME = column("Nom")
private val CATEGORY = column("Catégorie")
private val SHORT = column("Diminutif")
private val GENDER = column("Sexe")
private val RACE = column("Race")
@ -60,6 +60,7 @@ class LexiconParser @Inject constructor(
private val COLUMNS
get() = listOf(
NAME,
CATEGORY,
SHORT,
GENDER,
RACE,

View file

@ -22,7 +22,7 @@ class QuestParser @Inject constructor(
val entry = QuestEntry(
sheetIndex = index,
title = quest,
group = item.parse(column = GROUP),
group = item.parse(column = CATEGORY),
subtitle = item.parse(column = SUB_TITLE),
complete = item.parseBool(column = COMPLETED) ?: false,
questGiver = item.parse(column = QUEST_GIVER),
@ -44,7 +44,7 @@ class QuestParser @Inject constructor(
Quest(
id = "$quest-1", // TODO refactor that when quest have ids in the google sheet.
title = quest,
group = relatedEntries.firstNotNullOfOrNull { it.group },
category = relatedEntries.firstNotNullOfOrNull { it.group },
entries = relatedEntries,
)
}
@ -54,7 +54,7 @@ class QuestParser @Inject constructor(
companion object {
private val TITLE = column("Titre")
private val GROUP = column("Groupe")
private val CATEGORY = column("Catégorie")
private val SUB_TITLE = column("Sous Titre")
private val COMPLETED = column("Compléter")
private val QUEST_GIVER = column("Commanditaire")
@ -68,7 +68,7 @@ class QuestParser @Inject constructor(
private val COLUMNS
get() = listOf(
TITLE,
GROUP,
CATEGORY,
SUB_TITLE,
COMPLETED,
QUEST_GIVER,

View file

@ -1,42 +0,0 @@
package com.pixelized.rplexicon.data.parser
import com.pixelized.rplexicon.data.model.Lexicon
import javax.inject.Inject
class RaceParser @Inject constructor() {
fun parser(race: String?): Lexicon.Race {
return when (race?.takeIf { it.isNotBlank() }) {
Race.ELF -> Lexicon.Race.ELF
Race.HALFLING -> Lexicon.Race.HALFLING
Race.HUMAN -> Lexicon.Race.HUMAN
Race.DWARF -> Lexicon.Race.DWARF
Race.HALF_ELF -> Lexicon.Race.HALF_ELF
Race.HALF_ORC -> Lexicon.Race.HALF_ORC
Race.DRAGONBORN -> Lexicon.Race.DRAGONBORN
Race.GNOME -> Lexicon.Race.GNOME
Race.TIEFLING -> Lexicon.Race.TIEFLING
Race.AARAKOCRA -> Lexicon.Race.AARAKOCRA
Race.GENASI -> Lexicon.Race.GENASI
Race.DEEP_GNOME -> Lexicon.Race.DEEP_GNOME
Race.GOLIATH -> Lexicon.Race.GOLIATH
else -> Lexicon.Race.UNDETERMINED
}
}
private object Race {
const val ELF = "Elfe"
const val HALFLING = "Halfelin"
const val HUMAN = "Humain"
const val DWARF = "Nain"
const val HALF_ELF = "Demi-Elfe"
const val HALF_ORC = "Demi-Orc"
const val DRAGONBORN = "Drakéide"
const val GNOME = "Gnome"
const val TIEFLING = "Tieffelin"
const val AARAKOCRA = "Aarakocra"
const val GENASI = "Génasi"
const val DEEP_GNOME = "Gnome des Profondeurs"
const val GOLIATH = "Goliath"
}
}

View file

@ -21,7 +21,7 @@ class CategoryOrderRepository @Inject constructor(
var lastSuccessFullUpdate: Update = Update.INITIAL
private set
fun finLexiconOrder(quest: String?): Int {
fun findLexiconOrder(quest: String?): Int {
return _data.value[LEXICON]?.get(quest) ?: Int.MAX_VALUE
}

View file

@ -1,176 +0,0 @@
package com.pixelized.rplexicon.ui.composable.form
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Lexicon
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.composable.stringResource
import com.pixelized.rplexicon.utilitary.extentions.lexicon
@Stable
data class DropDownFieldUio<T>(
@StringRes val label: Int,
val values: List<T>,
val value: State<T?>,
val valueLabel: @Composable (T) -> String,
val onValueChange: (T?) -> Unit,
) {
companion object {
fun <T> preview(
@StringRes label: Int,
id: T?,
valueLabel: @Composable (T) -> String,
) = DropDownFieldUio(
label = label,
values = emptyList(),
value = mutableStateOf(id),
valueLabel = valueLabel,
onValueChange = { },
)
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalAnimationApi::class)
@Composable
fun <T> DropDownField(
modifier: Modifier = Modifier,
field: DropDownFieldUio<T>,
) {
var expended by remember(field) { mutableStateOf(false) }
ExposedDropdownMenuBox(
modifier = modifier,
expanded = expended,
onExpandedChange = { expended = !expended && field.value.value == null },
) {
OutlinedTextField(
modifier = Modifier.menuAnchor(),
shape = MaterialTheme.lexicon.shapes.textField,
readOnly = true,
singleLine = true,
label = {
Text(
text = stringResource(id = field.label)
)
},
trailingIcon = {
AnimatedContent(
modifier = Modifier.size(size = 48.dp),
targetState = field.value.value != null,
transitionSpec = { fadeIn() togetherWith fadeOut() },
label = "DropDownFieldTrailingIconAnimation",
) {
when (it) {
true -> IconButton(
onClick = { field.onValueChange(null) },
) {
Icon(
modifier = Modifier.size(size = 18.dp),
painter = painterResource(id = R.drawable.ic_clear_24),
contentDescription = null,
)
}
else -> Box(
contentAlignment = Alignment.Center,
) {
Icon(
modifier = Modifier.size(size = 24.dp),
painter = painterResource(id = R.drawable.ic_arrow_down_24),
contentDescription = null,
)
}
}
}
},
colors = TextFieldDefaults.colors(
focusedContainerColor = MaterialTheme.colorScheme.surface,
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
),
value = field.value.value?.let { field.valueLabel(it) } ?: "",
onValueChange = {},
)
DropdownMenu(
expanded = expended,
onDismissRequest = { expended = false },
) {
field.values.forEach {
DropdownMenuItem(
onClick = {
expended = false
field.onValueChange(it)
},
text = {
Text(text = field.valueLabel(it))
},
)
}
}
}
}
@Composable
@Preview(uiMode = UI_MODE_NIGHT_NO)
@Preview(uiMode = UI_MODE_NIGHT_YES)
private fun DropDownFieldPreview(
@PreviewParameter(DropDownFieldPreviewProvider::class) preview: Lexicon.Race?,
) {
LexiconTheme {
Surface {
DropDownField(
modifier = Modifier
.fillMaxWidth()
.padding(all = 8.dp),
field = DropDownFieldUio(
label = R.string.search_field_race,
values = emptyList(),
value = remember { mutableStateOf(preview) },
valueLabel = { stringResource(id = it) },
onValueChange = { },
)
)
}
}
}
private class DropDownFieldPreviewProvider : PreviewParameterProvider<Lexicon.Race?> {
override val values: Sequence<Lexicon.Race?> = sequenceOf(null, Lexicon.Race.HALF_ORC)
}

View file

@ -8,7 +8,6 @@ import androidx.navigation.NavHostController
import androidx.navigation.NavOptionsBuilder
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.pixelized.rplexicon.data.model.Lexicon
import com.pixelized.rplexicon.ui.navigation.NavigationAnimation
import com.pixelized.rplexicon.ui.navigation.animatedComposable
import com.pixelized.rplexicon.ui.screens.lexicon.detail.LexiconDetailScreen
@ -17,42 +16,21 @@ import com.pixelized.rplexicon.utilitary.extentions.ARG
private const val ROUTE = "LexiconDetail"
private const val ARG_ID = "id"
private const val ARG_HIGHLIGHT = "highlight"
private const val ARG_RACE = "race"
private const val ARG_HIGHLIGHT_RACE = "highlightRace"
private const val ARG_GENDER = "gender"
private const val ARG_HIGHLIGHT_GENDER = "highlightGender"
val LEXICON_DETAIL_ROUTE = ROUTE +
"?${ARG_ID.ARG}" +
"&${ARG_HIGHLIGHT.ARG}" +
"&${ARG_RACE.ARG}" +
"&${ARG_HIGHLIGHT_RACE.ARG}" +
"&${ARG_GENDER.ARG}" +
"&${ARG_HIGHLIGHT_GENDER.ARG}"
"&${ARG_HIGHLIGHT.ARG}"
@Stable
@Immutable
data class LexiconDetailArgument(
val id: String,
val highlight: String?,
val race: Lexicon.Race,
val highlightRace: Boolean,
val gender: Lexicon.Gender,
val highlightGender: Boolean,
)
val SavedStateHandle.lexiconDetailArgument: LexiconDetailArgument
get() = LexiconDetailArgument(
id = get(ARG_ID)
?: error("CharacterDetailArgument argument: $ARG_ID"),
race = get(ARG_RACE)
?: error("CharacterDetailArgument argument: $ARG_RACE"),
highlightRace = get(ARG_HIGHLIGHT_RACE)
?: error("CharacterDetailArgument argument: $ARG_HIGHLIGHT_RACE"),
gender = get(ARG_GENDER)
?: error("CharacterDetailArgument argument: $ARG_GENDER"),
highlightGender = get(ARG_HIGHLIGHT_GENDER)
?: error("CharacterDetailArgument argument: $ARG_HIGHLIGHT_GENDER"),
id = get(ARG_ID) ?: error("CharacterDetailArgument argument: $ARG_ID"),
highlight = get(ARG_HIGHLIGHT),
)
@ -67,18 +45,6 @@ fun NavGraphBuilder.composableLexiconDetail() {
type = NavType.StringType
nullable = true
},
navArgument(name = ARG_RACE) {
type = NavType.EnumType(Lexicon.Race::class.java)
},
navArgument(name = ARG_HIGHLIGHT_RACE) {
type = NavType.BoolType
},
navArgument(name = ARG_GENDER) {
type = NavType.EnumType(Lexicon.Gender::class.java)
},
navArgument(name = ARG_HIGHLIGHT_GENDER) {
type = NavType.BoolType
},
),
animation = NavigationAnimation.Push,
) {
@ -89,17 +55,11 @@ fun NavGraphBuilder.composableLexiconDetail() {
fun NavHostController.navigateToLexiconDetail(
id: String,
highlight: String? = null,
race: Lexicon.Race? = null,
gender: Lexicon.Gender? = null,
option: NavOptionsBuilder.() -> Unit = {},
) {
val route = ROUTE +
"?$ARG_ID=$id" +
"&$ARG_HIGHLIGHT=$highlight" +
"&$ARG_RACE=${race ?: Lexicon.Race.UNDETERMINED}" +
"&$ARG_HIGHLIGHT_RACE=${race != null}" +
"&$ARG_GENDER=${gender ?: Lexicon.Gender.UNDETERMINED}" +
"&$ARG_HIGHLIGHT_GENDER=${gender != null}"
"&$ARG_HIGHLIGHT=$highlight"
navigate(route = route, builder = option)
}

View file

@ -55,7 +55,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Lexicon
import com.pixelized.rplexicon.ui.composable.AsyncImage
import com.pixelized.rplexicon.ui.composable.BackgroundImage
import com.pixelized.rplexicon.ui.composable.FullScreenImageHandler
@ -63,7 +62,6 @@ import com.pixelized.rplexicon.ui.composable.FullScreenImageViewModel
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.navigation.screens.navigateToCharacterSheet
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.composable.stringResource
import com.pixelized.rplexicon.utilitary.extentions.annotatedSpan
import com.pixelized.rplexicon.utilitary.extentions.annotatedString
import com.pixelized.rplexicon.utilitary.extentions.highlightRegex
@ -75,16 +73,14 @@ import com.pixelized.rplexicon.utilitary.extentions.searchCriterion
data class LexiconDetailUio(
val name: String,
val diminutive: String?,
val gender: Lexicon.Gender,
val race: Lexicon.Race,
val gender: String?,
val race: String?,
val status: String?,
val location: String?,
val portrait: List<Uri>,
val description: String?,
val history: String?,
val search: String?,
val highlightGender: Boolean?,
val highlightRace: Boolean?,
val tags: String?,
)
@ -92,8 +88,8 @@ data class LexiconDetailUio(
data class AnnotatedLexiconDetailUio(
val name: AnnotatedString,
val diminutive: AnnotatedString?,
val gender: AnnotatedString,
val race: AnnotatedString,
val gender: AnnotatedString?,
val race: AnnotatedString?,
val portrait: List<Uri>,
val status: AnnotatedString?,
val location: AnnotatedString?,
@ -110,10 +106,8 @@ fun LexiconDetailUio.annotate(): AnnotatedLexiconDetailUio {
val highlight = remember { SpanStyle(color = colorScheme.primary) }
val trimmedSearch = remember(search) { search.searchCriterion() }
val highlightRegex = remember(search) { trimmedSearch.highlightRegex }
val gender = stringResource(id = gender, short = true)
val race = stringResource(id = race)
return remember(search, race, highlightRace, gender, highlightGender) {
return remember(search) {
AnnotatedLexiconDetailUio(
portrait = portrait,
name = AnnotatedString(
@ -132,20 +126,12 @@ fun LexiconDetailUio.annotate(): AnnotatedLexiconDetailUio {
diminutive = diminutive?.let {
highlightRegex.annotatedString(input = it, spanStyle = highlight)
},
gender = AnnotatedString(
text = gender,
spanStyles = when (highlightGender) {
true -> listOf(AnnotatedString.Range(highlight, 0, gender.length))
else -> emptyList()
}
),
race = AnnotatedString(
text = race,
spanStyles = when (highlightRace) {
true -> listOf(AnnotatedString.Range(highlight, 0, race.length))
else -> emptyList()
}
),
gender = gender?.let { gender ->
highlightRegex.annotatedString(input = gender, spanStyle = highlight)
},
race = race?.let { race ->
highlightRegex.annotatedString(input = race, spanStyle = highlight)
},
description = description?.let { description ->
highlightRegex.annotatedString(input = description, spanStyle = highlight)
},
@ -457,8 +443,8 @@ private fun LexiconDetailPreview() {
LexiconDetailUio(
name = "Brulkhai",
diminutive = "./ Bru",
gender = Lexicon.Gender.FEMALE,
race = Lexicon.Race.HALF_ORC,
gender = "Female",
race = "Half-orc",
status = "Vivante",
location = "Manoir Durst",
portrait = listOf(
@ -468,8 +454,6 @@ private fun LexiconDetailPreview() {
history = null,
tags = "protagoniste, brute",
search = "Bru",
highlightGender = true,
highlightRace = true,
)
)
}

View file

@ -38,8 +38,6 @@ class LexiconDetailViewModel @Inject constructor(
history = source.history,
tags = source.tags,
search = argument.highlight,
highlightGender = argument.highlightGender && argument.gender == source.gender,
highlightRace = argument.highlightRace && argument.race == source.race,
)
}

View file

@ -0,0 +1,26 @@
package com.pixelized.rplexicon.ui.screens.lexicon.list
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
@Stable
data class LexiconCategoryUio(
val title: String,
)
@Composable
fun LexiconCategory(
modifier: Modifier = Modifier,
item: LexiconCategoryUio,
) {
Text(
modifier = modifier,
style = MaterialTheme.typography.labelSmall,
fontWeight = FontWeight.Light,
text = item.title,
)
}

View file

@ -0,0 +1,9 @@
package com.pixelized.rplexicon.ui.screens.lexicon.list
import androidx.compose.runtime.Stable
@Stable
class LexiconGroupUio(
val category: LexiconCategoryUio?,
val items: List<LexiconItemUio>,
)

View file

@ -2,10 +2,8 @@ package com.pixelized.rplexicon.ui.screens.lexicon.list
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
@ -15,7 +13,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
@ -24,7 +21,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.LOS_FULL
import com.pixelized.rplexicon.utilitary.LOS_HOLLOW
@ -37,8 +33,8 @@ data class LexiconItemUio(
val id: String,
val name: String,
val diminutive: String?,
@StringRes val gender: Int,
@StringRes val race: Int,
val gender: String?,
val race: String?,
val isPlayingCharacter: Boolean = false,
val placeholder: Boolean = false,
) {
@ -48,8 +44,8 @@ data class LexiconItemUio(
id: String = "Brulkhai-1",
name: String = "Brulkhai",
diminutive: String? = null,
@StringRes gender: Int = R.string.gender_female_short,
@StringRes race: Int = R.string.race_half_orc,
gender: String? = null,
race: String? = null,
isPlayingCharacter: Boolean = false,
placeholder: Boolean = false,
) = LexiconItemUio(
@ -122,27 +118,31 @@ fun LexiconItem(
)
}
Text(
modifier = Modifier
.alignByBaseline()
.placeholder { item.placeholder },
style = typography.base.labelMedium,
fontStyle = FontStyle.Italic,
fontWeight = FontWeight.Light,
maxLines = 1,
text = stringResource(id = item.gender)
)
item.gender?.let {
Text(
modifier = Modifier
.alignByBaseline()
.placeholder { item.placeholder },
style = typography.base.labelMedium,
fontStyle = FontStyle.Italic,
fontWeight = FontWeight.Light,
maxLines = 1,
text = it,
)
}
Text(
modifier = Modifier
.alignByBaseline()
.placeholder { item.placeholder },
style = typography.base.labelMedium,
fontStyle = FontStyle.Italic,
fontWeight = FontWeight.Light,
maxLines = 1,
text = stringResource(id = item.race)
)
item.race?.let {
Text(
modifier = Modifier
.alignByBaseline()
.placeholder { item.placeholder },
style = typography.base.labelMedium,
fontStyle = FontStyle.Italic,
fontWeight = FontWeight.Light,
maxLines = 1,
text = it,
)
}
}
}
}

View file

@ -43,6 +43,7 @@ import com.pixelized.rplexicon.ui.composable.error.HandleFetchError
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconDetail
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconSearch
import com.pixelized.rplexicon.ui.screens.quest.list.QuestCategory
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.cell
import com.pixelized.rplexicon.utilitary.extentions.lexicon
@ -102,7 +103,7 @@ private fun LexiconScreenContent(
refreshState: PullRefreshState,
refreshing: State<Boolean>,
isFabExpended: State<Boolean>,
items: State<List<LexiconItemUio>>,
items: State<List<LexiconGroupUio>>,
onSearch: () -> Unit,
onItem: (LexiconItemUio) -> Unit,
) {
@ -137,17 +138,31 @@ private fun LexiconScreenContent(
state = lazyColumnState,
contentPadding = MaterialTheme.lexicon.dimens.itemListPadding,
) {
items(
items = items.value,
key = { it.id },
contentType = { "Lexicon" },
) {
LexiconItem(
modifier = Modifier
.clickable { onItem(it) }
.cell(),
item = it,
)
items.value.forEachIndexed { index, entry ->
entry.category?.let {
item(
contentType = { "Header" },
) {
LexiconCategory(
modifier = Modifier
.padding(top = if (index == 0) 0.dp else 16.dp)
.padding(horizontal = 16.dp),
item = it,
)
}
}
items(
items = entry.items,
key = { it.id },
contentType = { "Lexicon" },
) {
LexiconItem(
modifier = Modifier
.clickable { onItem(it) }
.cell(),
item = it,
)
}
}
}
}
@ -206,13 +221,18 @@ private fun LexiconScreenContentPreview() {
items = remember {
mutableStateOf(
listOf(
LexiconItemUio(
id = "Brulkhai-1",
name = "Brulkhai",
diminutive = "Bru",
gender = R.string.gender_female_short,
race = R.string.race_half_orc,
)
LexiconGroupUio(
category = null,
items = listOf(
LexiconItemUio(
id = "Brulkhai-1",
name = "Brulkhai",
diminutive = "Bru",
gender = "Female",
race = "Half-orc",
)
),
),
)
)
},

View file

@ -1,36 +1,44 @@
package com.pixelized.rplexicon.ui.screens.lexicon.list
import android.app.Application
import android.util.Log
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Lexicon
import com.pixelized.rplexicon.data.repository.character.CharacterSheetRepository
import com.pixelized.rplexicon.data.repository.lexicon.CategoryOrderRepository
import com.pixelized.rplexicon.data.repository.lexicon.LexiconRepository
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
import com.pixelized.rplexicon.ui.screens.quest.list.QuestListViewModel
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import com.pixelized.rplexicon.utilitary.extentions.context
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@HiltViewModel
class LexiconViewModel @Inject constructor(
private val lexiconRepository: LexiconRepository,
private val characterSheetRepository: CharacterSheetRepository,
) : ViewModel() {
private val orderRepository: CategoryOrderRepository,
application: Application,
) : AndroidViewModel(application) {
private val _isLoading = mutableStateOf(false)
val isLoading: State<Boolean> get() = _isLoading
private val _items = mutableStateOf<List<LexiconItemUio>>(emptyList())
val items: State<List<LexiconItemUio>> get() = _items
private val _items = mutableStateOf<List<LexiconGroupUio>>(emptyList())
val items: State<List<LexiconGroupUio>> get() = _items
private val _error = MutableSharedFlow<FetchErrorUio>()
val error: SharedFlow<FetchErrorUio> get() = _error
@ -38,41 +46,36 @@ class LexiconViewModel @Inject constructor(
init {
viewModelScope.launch {
launch {
lexiconRepository.data.collect { items ->
_items.value = items
.sortedBy { it.name }
.sortedBy { !characterSheetRepository.haveSheet(it.name) }
.map { item ->
LexiconItemUio(
id = item.id,
name = item.name,
diminutive = item.diminutive?.takeIf { it.isNotBlank() }
?.let { "./ $it" },
gender = when (item.gender) {
Lexicon.Gender.MALE -> R.string.gender_male_short
Lexicon.Gender.FEMALE -> R.string.gender_female_short
Lexicon.Gender.UNDETERMINED -> R.string.gender_undetermined_short
orderRepository.data.combine(lexiconRepository.data) { _, lexicon -> lexicon }
.collect { items ->
_items.value = items
.sortedBy { it.name }
.groupBy(
keySelector = {
LexiconCategoryUio(
title = it.category
?: context.getString(R.string.default_category_other)
)
},
race = when (item.race) {
Lexicon.Race.ELF -> R.string.race_elf
Lexicon.Race.HALFLING -> R.string.race_halfling
Lexicon.Race.HUMAN -> R.string.race_human
Lexicon.Race.DWARF -> R.string.race_dwarf
Lexicon.Race.HALF_ELF -> R.string.race_half_elf
Lexicon.Race.HALF_ORC -> R.string.race_half_orc
Lexicon.Race.DRAGONBORN -> R.string.race_dragonborn
Lexicon.Race.GNOME -> R.string.race_gnome
Lexicon.Race.TIEFLING -> R.string.race_tiefling
Lexicon.Race.AARAKOCRA -> R.string.race_aarakocra
Lexicon.Race.GENASI -> R.string.race_genasi
Lexicon.Race.DEEP_GNOME -> R.string.race_deep_gnome
Lexicon.Race.GOLIATH -> R.string.race_goliath
Lexicon.Race.UNDETERMINED -> R.string.race_undetermined
valueTransform = { item ->
LexiconItemUio(
id = item.id,
name = item.name,
diminutive = item.diminutive?.let { "./ $it" },
gender = item.gender,
race = item.race,
isPlayingCharacter = characterSheetRepository.haveSheet(item.name)
)
},
isPlayingCharacter = characterSheetRepository.haveSheet(item.name)
)
}
}
.map { item ->
LexiconGroupUio(
category = item.key,
items = item.value,
)
}
.sortedBy { orderRepository.findLexiconOrder(quest = it.category?.title) }
}
}
launch {
updateLexicon(force = false)
@ -81,15 +84,19 @@ class LexiconViewModel @Inject constructor(
}
suspend fun updateLexicon(force: Boolean) = coroutineScope {
_isLoading.value = true
val lexicon = async {
fetchLexicon(force = force)
withContext(Dispatchers.Main) {
_isLoading.value = true
}
val sheets = async {
fetchCharacterSheet(force = force)
withContext(Dispatchers.IO) {
awaitAll(
async { fetchLexicon(force = force) },
async { fetchCharacterSheet(force = force) },
async { fetchCategoryOrder(force = force) },
)
}
withContext(Dispatchers.Main) {
_isLoading.value = false
}
awaitAll(lexicon, sheets)
_isLoading.value = false
}
private suspend fun fetchLexicon(force: Boolean) {
@ -128,6 +135,24 @@ class LexiconViewModel @Inject constructor(
}
}
private suspend fun fetchCategoryOrder(force: Boolean) {
try {
if (force || orderRepository.lastSuccessFullUpdate.shouldUpdate()) {
orderRepository.fetchCategoryOrder()
}
}
// the data sheet structure is not as expected
catch (exception: IncompatibleSheetStructure) {
Log.e(QuestListViewModel.TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure(type = FetchErrorUio.Structure.Type.CATEGORY_ORDER))
}
// default exception
catch (exception: Exception) {
Log.e(QuestListViewModel.TAG, exception.message, exception)
_error.emit(FetchErrorUio.Default)
}
}
companion object {
private const val TAG = "LexiconViewModel"
}

View file

@ -4,11 +4,8 @@ import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@ -30,9 +27,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Lexicon
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.composable.stringResource
import com.pixelized.rplexicon.utilitary.extentions.annotatedSpan
import com.pixelized.rplexicon.utilitary.extentions.annotatedString
import com.pixelized.rplexicon.utilitary.extentions.finderRegex
@ -46,31 +41,27 @@ class SearchItemUio(
val id: String,
val name: String,
val diminutive: String?,
val gender: Lexicon.Gender,
val race: Lexicon.Race,
val gender: String?,
val race: String?,
val status: String?,
val location: String?,
val description: String?,
val history: String?,
val tags: String?,
val search: String,
val highlightGender: Boolean,
val highlightRace: Boolean,
) {
companion object {
fun preview(
id: String = "Brulkhai-1",
name: String = "Brulkhai",
diminutive: String? = "Bru",
gender: Lexicon.Gender = Lexicon.Gender.FEMALE,
race: Lexicon.Race = Lexicon.Race.HALF_ORC,
gender: String? = "Female",
race: String? = "Half-Orc",
description: String? = "Brulkhai, ou plus simplement Bru, est une demi-orc de 38 ans solidement bâti. Elle mesure 192 cm pour 110 kg de muscles lorsquelle est en bonne santé. Elle a les cheveux châtains, les yeux noisettes et la peau couleur gris-vert typique de son espèce. Dun tempérament taciturne, elle parle peu et de façon concise. Elle est parfois brutale, aussi bien physiquement que verbalement, Elle ne prend cependant aucun plaisir à malmener ceux quelle considère plus faibles quelle.\n" +
"Dune nature simple et honnête, elle ne mâche pas ses mots et ne dissimule généralement pas ses pensées. Son intelligence modeste est plus le reflet dun manque déducation et dune capacité limitée à gérer ses émotions quà une débilité congénitale.\n" +
"Elle voue à la force un culte car cest par son expression quelle se sent vraiment vivante et éprouve de grandes difficultés vis à vis de ceux quelle nomme foshnu (bébé, chouineur en commun).",
history: String? = null,
search: String = " ",
highlightGender: Boolean = false,
highlightRace: Boolean = false,
search: String = "",
): SearchItemUio {
return SearchItemUio(
id = id,
@ -84,8 +75,6 @@ class SearchItemUio(
history = history,
tags = null,
search = search,
highlightGender = highlightGender,
highlightRace = highlightRace,
)
}
}
@ -96,8 +85,8 @@ class AnnotatedSearchItemUio(
val id: String,
val name: AnnotatedString,
val diminutive: AnnotatedString?,
val gender: AnnotatedString,
val race: AnnotatedString,
val gender: AnnotatedString?,
val race: AnnotatedString?,
val status: AnnotatedString?,
val location: AnnotatedString?,
val description: AnnotatedString?,
@ -113,10 +102,8 @@ private fun SearchItemUio.annotate(): AnnotatedSearchItemUio {
val trimmedSearch = remember(search) { search.searchCriterion() }
val highlightRegex = remember(search) { trimmedSearch.highlightRegex }
val finderRegex = remember(search) { trimmedSearch.finderRegex }
val gender = stringResource(id = gender, short = true)
val race = stringResource(id = race)
return remember(trimmedSearch, race, highlightRace, gender, highlightGender) {
return remember(trimmedSearch) {
AnnotatedSearchItemUio(
id = id,
name = AnnotatedString(
@ -130,23 +117,11 @@ private fun SearchItemUio.annotate(): AnnotatedSearchItemUio {
input = diminutive ?: "",
spanStyle = highlight
),
gender = gender.let {
AnnotatedString(
text = it,
spanStyles = when (highlightGender) {
true -> listOf(AnnotatedString.Range(highlight, 0, it.length))
else -> emptyList()
}
)
gender = finderRegex?.foldAll(gender)?.let { gender ->
highlightRegex?.annotatedString(gender, spanStyle = highlight)
},
race = race.let {
AnnotatedString(
text = it,
spanStyles = when (highlightRace) {
true -> listOf(AnnotatedString.Range(highlight, 0, it.length))
else -> emptyList()
}
)
race = finderRegex?.foldAll(race)?.let { race ->
highlightRegex?.annotatedString(race, spanStyle = highlight)
},
status = finderRegex?.foldAll(status)?.let { status ->
highlightRegex?.annotatedString(status, spanStyle = highlight)
@ -167,7 +142,6 @@ private fun SearchItemUio.annotate(): AnnotatedSearchItemUio {
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun SearchItem(
modifier: Modifier = Modifier,
@ -186,10 +160,10 @@ fun SearchItem(
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp)
.animateContentSize(),
verticalArrangement = Arrangement.spacedBy(6.dp),
verticalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
FlowRow(
horizontalArrangement = Arrangement.spacedBy(4.dp),
Row(
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
Text(
modifier = Modifier.alignByBaseline(),
@ -206,26 +180,24 @@ fun SearchItem(
text = it,
)
}
}
Row(
modifier = Modifier.offset(y = (-2).dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
modifier = Modifier.alignByBaseline(),
style = typography.labelMedium,
fontStyle = FontStyle.Italic,
maxLines = 1,
text = annotatedItem.gender
)
Text(
modifier = Modifier.alignByBaseline(),
style = typography.labelMedium,
fontStyle = FontStyle.Italic,
maxLines = 1,
text = annotatedItem.race
)
annotatedItem.gender?.let {
Text(
modifier = Modifier.alignByBaseline(),
style = typography.labelMedium,
fontStyle = FontStyle.Italic,
maxLines = 1,
text = it,
)
}
annotatedItem.race?.let {
Text(
modifier = Modifier.alignByBaseline(),
style = typography.labelMedium,
fontStyle = FontStyle.Italic,
maxLines = 1,
text = it,
)
}
}
Column(
@ -346,7 +318,7 @@ private class SearchItemPreviewProvider : PreviewParameterProvider<SearchItemUio
SearchItemUio.preview(search = "bru"),
SearchItemUio.preview(search = "Brulkhai"),
SearchItemUio.preview(search = "elle"),
SearchItemUio.preview(highlightGender = true),
SearchItemUio.preview(highlightRace = true),
SearchItemUio.preview(search = "female"),
SearchItemUio.preview(search = "orc"),
)
}

View file

@ -6,7 +6,6 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
@ -41,23 +40,17 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.NO_WINDOW_INSETS
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Lexicon
import com.pixelized.rplexicon.ui.composable.CollapsingHeader
import com.pixelized.rplexicon.ui.composable.form.DropDownField
import com.pixelized.rplexicon.ui.composable.form.DropDownFieldUio
import com.pixelized.rplexicon.ui.composable.form.TextField
import com.pixelized.rplexicon.ui.composable.form.TextFieldUio
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconDetail
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.composable.stringResource
import com.pixelized.rplexicon.utilitary.extentions.lexicon
@Stable
data class SearchFormUio(
val search: TextFieldUio,
val gender: DropDownFieldUio<Lexicon.Gender>,
val race: DropDownFieldUio<Lexicon.Race>,
)
@Composable
@ -81,8 +74,6 @@ fun SearchScreen(
screen.navigateToLexiconDetail(
id = item.id,
highlight = form.search.value.value.takeIf { it.isNotEmpty() },
race = form.race.value.value,
gender = form.gender.value.value,
)
},
onBack = {
@ -172,19 +163,6 @@ private fun SearchBox(
.padding(horizontal = 16.dp),
field = form.search,
)
Row(
modifier = Modifier.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
DropDownField(
modifier = Modifier.weight(weight = 1f, fill = true),
field = form.gender,
)
DropDownField(
modifier = Modifier.weight(weight = 1f, fill = true),
field = form.race,
)
}
Divider(
modifier = Modifier.padding(top = 16.dp),
color = MaterialTheme.lexicon.colorScheme.placeholder,
@ -203,16 +181,6 @@ private fun SearchScreenContentPreview() {
modifier = Modifier.fillMaxSize(),
form = SearchFormUio(
search = TextFieldUio.preview(R.string.search_field_search),
gender = DropDownFieldUio.preview(
id = Lexicon.Gender.FEMALE,
label = R.string.search_field_gender,
valueLabel = { stringResource(id = Lexicon.Gender.FEMALE) },
),
race = DropDownFieldUio.preview(
id = null,
label = R.string.search_field_race,
valueLabel = { stringResource(id = Lexicon.Race.HALF_ORC) },
),
),
items = remember {
mutableStateOf(
@ -221,36 +189,36 @@ private fun SearchScreenContentPreview() {
id = "Brulkhai-1",
name = "Brulkhai",
diminutive = "Bru",
gender = Lexicon.Gender.FEMALE,
race = Lexicon.Race.HALF_ORC,
gender = "Female",
race = "Half-orc",
),
SearchItemUio.preview(
id = "Léandre-1",
name = "Léandre",
diminutive = null,
gender = Lexicon.Gender.MALE,
race = Lexicon.Race.HUMAN,
gender = "Male",
race = "Human",
),
SearchItemUio.preview(
id = "Nélia-1",
name = "Nélia",
diminutive = null,
gender = Lexicon.Gender.FEMALE,
race = Lexicon.Race.ELF,
gender = "Female",
race = "Elf",
),
SearchItemUio.preview(
id = "Tigrane-1",
name = "Tigrane",
diminutive = null,
gender = Lexicon.Gender.MALE,
race = Lexicon.Race.TIEFLING,
gender = "Male",
race = "Tiefling",
),
SearchItemUio.preview(
id = "Unathana-1",
name = "Unathana",
diminutive = "Una",
gender = Lexicon.Gender.FEMALE,
race = Lexicon.Race.HALF_ELF,
gender = "Female",
race = "Half-elf",
),
)
)

View file

@ -5,12 +5,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Lexicon
import com.pixelized.rplexicon.data.model.Lexicon.Gender
import com.pixelized.rplexicon.data.model.Lexicon.Race
import com.pixelized.rplexicon.data.repository.lexicon.LexiconRepository
import com.pixelized.rplexicon.ui.composable.form.DropDownFieldUio
import com.pixelized.rplexicon.ui.composable.form.TextFieldUio
import com.pixelized.rplexicon.utilitary.composable.stringResource
import com.pixelized.rplexicon.utilitary.extentions.searchCriterion
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@ -20,8 +16,6 @@ class SearchViewModel @Inject constructor(
repository: LexiconRepository,
) : ViewModel() {
private val _search = mutableStateOf("")
private val _gender = mutableStateOf<Gender?>(null)
private val _race = mutableStateOf<Race?>(null)
val form = SearchFormUio(
search = TextFieldUio(
@ -31,39 +25,25 @@ class SearchViewModel @Inject constructor(
_search.value = it
}
),
gender = DropDownFieldUio(
label = R.string.search_field_gender,
values = Lexicon.Gender.values().toList(),
value = _gender,
valueLabel = { stringResource(id = it) },
onValueChange = { id -> _gender.value = id },
),
race = DropDownFieldUio(
label = R.string.search_field_race,
values = Lexicon.Race.values().toList(),
value = _race,
valueLabel = { stringResource(id = it) },
onValueChange = { id -> _race.value = id },
),
)
private var data: List<Lexicon> = repository.data.value
val filter = derivedStateOf {
data.filter { item ->
val gender = _gender.value?.let { it == item.gender }
val race = _race.value?.let { it == item.race }
val search = _search.value.searchCriterion().map { criteria ->
val name = item.name.contains(criteria, true)
val gender = item.gender?.contains(criteria, true) == true
val race = item.race?.contains(criteria, true) == true
val diminutive = item.diminutive?.contains(criteria, true) == true
val status = item.status?.contains(criteria, true) == true
val location = item.location?.contains(criteria, true) == true
val description = item.description?.contains(criteria, true) == true
val history = item.history?.contains(criteria, true) == true
val tag = item.tags?.contains(criteria, true) == true
name || diminutive || status || location || description || history || tag
name || gender || race || diminutive || status || location || description || history || tag
}
(gender == null || gender) && (race == null || race) && (search.all { it })
search.all { it }
}.map {
it.toSearchUio()
}.sortedBy {
@ -73,8 +53,6 @@ class SearchViewModel @Inject constructor(
private fun Lexicon.toSearchUio(
search: String = _search.value,
highlightGender: Boolean = this.gender == _gender.value,
highlightRace: Boolean = this.race == _race.value,
) = SearchItemUio(
id = this.id,
name = this.name,
@ -86,8 +64,6 @@ class SearchViewModel @Inject constructor(
description = this.description,
history = this.history,
search = search,
highlightGender = highlightGender,
highlightRace = highlightRace,
tags = tags,
)
}

View file

@ -16,6 +16,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.combine
@ -51,7 +52,7 @@ class QuestListViewModel @Inject constructor(
.groupBy(
keySelector = {
QuestCategoryUio(
title = it.group
title = it.category
?: context.getString(R.string.default_category_other),
)
},
@ -74,36 +75,27 @@ class QuestListViewModel @Inject constructor(
}
}
launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {
_isLoading.value = true
}
awaitAll(
async { updateQuests(force = false) },
async { updateCategoryOrder(force = false) },
)
withContext(Dispatchers.Main) {
_isLoading.value = false
}
update(force = false)
}
}
}
fun update(force: Boolean) {
viewModelScope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {
_isLoading.value = true
}
suspend fun update(force: Boolean) = coroutineScope {
withContext(Dispatchers.Main) {
_isLoading.value = true
}
withContext(Dispatchers.IO) {
awaitAll(
async { updateQuests(force = force) },
async { updateCategoryOrder(force = force) },
async { fetchQuests(force = force) },
async { fetchCategoryOrder(force = force) },
)
withContext(Dispatchers.Main) {
_isLoading.value = false
}
}
withContext(Dispatchers.Main) {
_isLoading.value = false
}
}
private suspend fun updateQuests(force: Boolean) {
private suspend fun fetchQuests(force: Boolean) {
try {
if (force || repository.lastSuccessFullUpdate.shouldUpdate()) {
repository.fetchQuests()
@ -121,7 +113,7 @@ class QuestListViewModel @Inject constructor(
}
}
private suspend fun updateCategoryOrder(force: Boolean) {
private suspend fun fetchCategoryOrder(force: Boolean) {
try {
if (force || order.lastSuccessFullUpdate.shouldUpdate()) {
order.fetchCategoryOrder()

View file

@ -1,27 +0,0 @@
package com.pixelized.rplexicon.utilitary.composable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Lexicon
import androidx.compose.ui.res.stringResource as androidStringResource
@Composable
@ReadOnlyComposable
fun stringResource(id: Lexicon.Gender, short: Boolean = false): String {
return androidStringResource(
id = when (short) {
true -> when (id) {
Lexicon.Gender.MALE -> R.string.gender_male_short
Lexicon.Gender.FEMALE -> R.string.gender_female_short
Lexicon.Gender.UNDETERMINED -> R.string.gender_undetermined_short
}
else -> when (id) {
Lexicon.Gender.MALE -> R.string.gender_male
Lexicon.Gender.FEMALE -> R.string.gender_female
Lexicon.Gender.UNDETERMINED -> R.string.gender_undetermined
}
}
)
}

View file

@ -1,30 +0,0 @@
package com.pixelized.rplexicon.utilitary.composable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.model.Lexicon
import androidx.compose.ui.res.stringResource as androidStringResource
@Composable
@ReadOnlyComposable
fun stringResource(id: Lexicon.Race): String {
return androidStringResource(
id = when (id) {
Lexicon.Race.ELF -> R.string.race_elf
Lexicon.Race.HALFLING -> R.string.race_halfling
Lexicon.Race.HUMAN -> R.string.race_human
Lexicon.Race.DWARF -> R.string.race_dwarf
Lexicon.Race.HALF_ELF -> R.string.race_half_elf
Lexicon.Race.HALF_ORC -> R.string.race_half_orc
Lexicon.Race.DRAGONBORN -> R.string.race_dragonborn
Lexicon.Race.GNOME -> R.string.race_gnome
Lexicon.Race.TIEFLING -> R.string.race_tiefling
Lexicon.Race.AARAKOCRA -> R.string.race_aarakocra
Lexicon.Race.GENASI -> R.string.race_genasi
Lexicon.Race.DEEP_GNOME -> R.string.race_deep_gnome
Lexicon.Race.GOLIATH -> R.string.race_goliath
Lexicon.Race.UNDETERMINED -> R.string.race_undetermined
}
)
}

View file

@ -25,28 +25,6 @@
<string name="generic_failure">Échec</string>
<string name="generic_failure_critical">ÉCHEC CRITIQUE</string>
<string name="gender_male">Mâle</string>
<string name="gender_female">Femelle</string>
<string name="gender_undetermined">Indéterminé</string>
<string name="gender_male_short">m.</string>
<string name="gender_female_short">f.</string>
<string name="gender_undetermined_short">i.</string>
<string name="race_elf">Elfe</string>
<string name="race_halfling">Halfelin</string>
<string name="race_human">Humain</string>
<string name="race_dwarf">Nain</string>
<string name="race_half_elf">Demi-Elfe</string>
<string name="race_half_orc">Demi-Orc</string>
<string name="race_dragonborn">Drakéide</string>
<string name="race_gnome">Gnome</string>
<string name="race_tiefling">Tieffelin</string>
<string name="race_aarakocra">Aarakocra</string>
<string name="race_genasi">Génasi</string>
<string name="race_deep_gnome">Gnome des Profondeurs</string>
<string name="race_goliath">Goliath</string>
<string name="race_undetermined">Indéterminé</string>
<string name="spell_school_abjuration">Abjuration</string>
<string name="spell_school_divination">Divination</string>
<string name="spell_school_enchantment">Enchantement</string>

View file

@ -25,28 +25,6 @@
<string name="generic_failure">Failure</string>
<string name="generic_failure_critical">CRITICAL FAILURE</string>
<string name="gender_male">Male</string>
<string name="gender_female">Female</string>
<string name="gender_undetermined">Undetermined</string>
<string name="gender_male_short">m.</string>
<string name="gender_female_short">f.</string>
<string name="gender_undetermined_short">u.</string>
<string name="race_elf">Elf</string>
<string name="race_halfling">Halfling</string>
<string name="race_human">Human</string>
<string name="race_dwarf">Dwarf</string>
<string name="race_half_elf">Half-elf</string>
<string name="race_half_orc">Half-Orc</string>
<string name="race_dragonborn">Dragonborn</string>
<string name="race_gnome">Gnome</string>
<string name="race_tiefling">Tiefling</string>
<string name="race_aarakocra">Aarakocra</string>
<string name="race_genasi">Genasi</string>
<string name="race_deep_gnome">Deep Gnome</string>
<string name="race_goliath">Goliath</string>
<string name="race_undetermined">Undetermined</string>
<string name="spell_school_abjuration">Abjuration</string>
<string name="spell_school_divination">Divination</string>
<string name="spell_school_enchantment">Enchantment</string>