From fa7fcbeae6d43a9c42d065f825c8cc20c83c4e21 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Wed, 15 Nov 2023 14:16:26 +0100 Subject: [PATCH] Category management - lexicon --- .../pixelized/rplexicon/data/model/Lexicon.kt | 33 +--- .../pixelized/rplexicon/data/model/Quest.kt | 2 +- .../rplexicon/data/parser/GenderParser.kt | 20 -- .../rplexicon/data/parser/LexiconParser.kt | 9 +- .../rplexicon/data/parser/QuestParser.kt | 8 +- .../rplexicon/data/parser/RaceParser.kt | 42 ----- .../lexicon/CategoryOrderRepository.kt | 2 +- .../ui/composable/form/DropDownField.kt | 176 ------------------ .../screens/ComposableLexiconDetail.kt | 46 +---- .../lexicon/detail/LexiconDetailScreen.kt | 42 ++--- .../lexicon/detail/LexiconDetailViewModel.kt | 2 - .../screens/lexicon/list/LexiconCategory.kt | 26 +++ .../screens/lexicon/list/LexiconGroupUio.kt | 9 + .../ui/screens/lexicon/list/LexiconItem.kt | 56 +++--- .../ui/screens/lexicon/list/LexiconScreen.kt | 58 ++++-- .../screens/lexicon/list/LexiconViewModel.kt | 113 ++++++----- .../ui/screens/lexicon/search/SearchItem.kt | 98 ++++------ .../ui/screens/lexicon/search/SearchScreen.kt | 52 +----- .../screens/lexicon/search/SearchViewModel.kt | 32 +--- .../screens/quest/list/QuestListViewModel.kt | 38 ++-- .../composable/StringResource+Gender.kt | 27 --- .../composable/StringResource+Race.kt | 30 --- app/src/main/res/values-fr/strings.xml | 22 --- app/src/main/res/values/strings.xml | 22 --- 24 files changed, 266 insertions(+), 699 deletions(-) delete mode 100644 app/src/main/java/com/pixelized/rplexicon/data/parser/GenderParser.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/data/parser/RaceParser.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/composable/form/DropDownField.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconCategory.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconGroupUio.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/utilitary/composable/StringResource+Gender.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/utilitary/composable/StringResource+Race.kt diff --git a/app/src/main/java/com/pixelized/rplexicon/data/model/Lexicon.kt b/app/src/main/java/com/pixelized/rplexicon/data/model/Lexicon.kt index e6fe81d..a6244a5 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/model/Lexicon.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/model/Lexicon.kt @@ -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, 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, - } -} \ No newline at end of file +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/data/model/Quest.kt b/app/src/main/java/com/pixelized/rplexicon/data/model/Quest.kt index 975634f..08656cf 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/model/Quest.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/model/Quest.kt @@ -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, ) { diff --git a/app/src/main/java/com/pixelized/rplexicon/data/parser/GenderParser.kt b/app/src/main/java/com/pixelized/rplexicon/data/parser/GenderParser.kt deleted file mode 100644 index d21a02e..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/data/parser/GenderParser.kt +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/data/parser/LexiconParser.kt b/app/src/main/java/com/pixelized/rplexicon/data/parser/LexiconParser.kt index beae0af..a94c60d 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/parser/LexiconParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/parser/LexiconParser.kt @@ -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 = 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, diff --git a/app/src/main/java/com/pixelized/rplexicon/data/parser/QuestParser.kt b/app/src/main/java/com/pixelized/rplexicon/data/parser/QuestParser.kt index 92747ca..b4b565b 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/parser/QuestParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/parser/QuestParser.kt @@ -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, diff --git a/app/src/main/java/com/pixelized/rplexicon/data/parser/RaceParser.kt b/app/src/main/java/com/pixelized/rplexicon/data/parser/RaceParser.kt deleted file mode 100644 index 5439101..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/data/parser/RaceParser.kt +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/data/repository/lexicon/CategoryOrderRepository.kt b/app/src/main/java/com/pixelized/rplexicon/data/repository/lexicon/CategoryOrderRepository.kt index 62bcfd6..9dc007e 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/repository/lexicon/CategoryOrderRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/repository/lexicon/CategoryOrderRepository.kt @@ -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 } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/composable/form/DropDownField.kt b/app/src/main/java/com/pixelized/rplexicon/ui/composable/form/DropDownField.kt deleted file mode 100644 index 2f39c20..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/ui/composable/form/DropDownField.kt +++ /dev/null @@ -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( - @StringRes val label: Int, - val values: List, - val value: State, - val valueLabel: @Composable (T) -> String, - val onValueChange: (T?) -> Unit, -) { - companion object { - fun 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 DropDownField( - modifier: Modifier = Modifier, - field: DropDownFieldUio, -) { - 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 { - override val values: Sequence = sequenceOf(null, Lexicon.Race.HALF_ORC) -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableLexiconDetail.kt b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableLexiconDetail.kt index bd7db88..efb264f 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableLexiconDetail.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableLexiconDetail.kt @@ -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) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt index af75e88..9f8914e 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt @@ -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, 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, 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, ) ) } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailViewModel.kt index b20c2c9..075ba1d 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailViewModel.kt @@ -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, ) } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconCategory.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconCategory.kt new file mode 100644 index 0000000..51ceadb --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconCategory.kt @@ -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, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconGroupUio.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconGroupUio.kt new file mode 100644 index 0000000..f60ef43 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconGroupUio.kt @@ -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, +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconItem.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconItem.kt index bb2b458..3e22d80 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconItem.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconItem.kt @@ -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, + ) + } } } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconScreen.kt index fca9e51..3a67842 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconScreen.kt @@ -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, isFabExpended: State, - items: State>, + items: State>, 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", + ) + ), + ), ) ) }, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconViewModel.kt index 1249ccd..d1a7547 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/list/LexiconViewModel.kt @@ -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 get() = _isLoading - private val _items = mutableStateOf>(emptyList()) - val items: State> get() = _items + private val _items = mutableStateOf>(emptyList()) + val items: State> get() = _items private val _error = MutableSharedFlow() val error: SharedFlow 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" } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/search/SearchItem.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/search/SearchItem.kt index 7a4fcb2..8a27b6d 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/search/SearchItem.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/search/SearchItem.kt @@ -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 lorsqu’elle est en bonne santé. Elle a les cheveux châtains, les yeux noisettes et la peau couleur gris-vert typique de son espèce. D’un 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 qu’elle considère plus faibles qu’elle.\n" + "D’une 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 d’un manque d’éducation et d’une capacité limitée à gérer ses émotions qu’à une débilité congénitale.\n" + "Elle voue à la force un culte car c’est par son expression qu’elle se sent vraiment vivante et éprouve de grandes difficultés vis à vis de ceux qu’elle 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, - val race: DropDownFieldUio, ) @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", ), ) ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/search/SearchViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/search/SearchViewModel.kt index 3e9a95c..3151c92 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/search/SearchViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/search/SearchViewModel.kt @@ -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(null) - private val _race = mutableStateOf(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 = 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, ) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListViewModel.kt index dd6faa4..2b46e94 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/quest/list/QuestListViewModel.kt @@ -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() diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/composable/StringResource+Gender.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/composable/StringResource+Gender.kt deleted file mode 100644 index c617238..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/utilitary/composable/StringResource+Gender.kt +++ /dev/null @@ -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 - } - } - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/composable/StringResource+Race.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/composable/StringResource+Race.kt deleted file mode 100644 index 23b1303..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/utilitary/composable/StringResource+Race.kt +++ /dev/null @@ -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 - } - ) -} \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 635137e..f08937a 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -25,28 +25,6 @@ Échec ÉCHEC CRITIQUE - Mâle - Femelle - Indéterminé - m. - f. - i. - - Elfe - Halfelin - Humain - Nain - Demi-Elfe - Demi-Orc - Drakéide - Gnome - Tieffelin - Aarakocra - Génasi - Gnome des Profondeurs - Goliath - Indéterminé - Abjuration Divination Enchantement diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3020dc2..1b82d85 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,28 +25,6 @@ Failure CRITICAL FAILURE - Male - Female - Undetermined - m. - f. - u. - - Elf - Halfling - Human - Dwarf - Half-elf - Half-Orc - Dragonborn - Gnome - Tiefling - Aarakocra - Genasi - Deep Gnome - Goliath - Undetermined - Abjuration Divination Enchantment