Add Status & Location to the Lexicon Detail & Search

This commit is contained in:
Thomas Andres Gomez 2023-09-18 18:29:30 +02:00
parent 418224a482
commit c2e642292a
13 changed files with 218 additions and 40 deletions

View file

@ -28,13 +28,15 @@ class LexiconParser @Inject constructor(
row is List<*> -> {
val name = row.getOrNull(sheetStructure.name) as? String
val diminutive = row.getOrNull(sheetStructure.diminutive) as? String?
val gender = row.getOrNull(sheetStructure.gender) as? String?
val race = row.getOrNull(sheetStructure.race) as? String?
val portrait = row.getOrNull(sheetStructure.portrait) as? String?
val description = row.getOrNull(sheetStructure.description) as? String?
val history = row.getOrNull(sheetStructure.history) as? String?
val tags = row.getOrNull(sheetStructure.tags) as? String?
val diminutive = row.getOrNull(sheetStructure.diminutive) as? String
val gender = row.getOrNull(sheetStructure.gender) as? String
val race = row.getOrNull(sheetStructure.race) as? String
val portrait = row.getOrNull(sheetStructure.portrait) as? String
val status = row.getOrNull(sheetStructure.status) as? String
val location = row.getOrNull(sheetStructure.location) as? String
val description = row.getOrNull(sheetStructure.description) as? String
val history = row.getOrNull(sheetStructure.history) as? String
val tags = row.getOrNull(sheetStructure.tags) as? String
if (name != null) {
Lexicon(
@ -45,6 +47,8 @@ class LexiconParser @Inject constructor(
gender = genderParser.parse(gender),
race = raceParser.parser(race),
portrait = portraitParser.parse(portrait),
status = status?.takeIf { it.isNotBlank() },
location = location?.takeIf { it.isNotBlank() },
description = description?.takeIf { it.isNotBlank() },
history = history?.takeIf { it.isNotBlank() },
tags = tags?.takeIf { it.isNotBlank() },
@ -63,10 +67,12 @@ class LexiconParser @Inject constructor(
private val Map<String, Int>.diminutive: Int get() = getValue(COLUMNS[1])
private val Map<String, Int>.gender: Int get() = getValue(COLUMNS[2])
private val Map<String, Int>.race: Int get() = getValue(COLUMNS[3])
private val Map<String, Int>.portrait: Int get() = getValue(COLUMNS[4])
private val Map<String, Int>.description: Int get() = getValue(COLUMNS[5])
private val Map<String, Int>.history: Int get() = getValue(COLUMNS[6])
private val Map<String, Int>.tags: Int get() = getValue(COLUMNS[7])
private val Map<String, Int>.status: Int get() = getValue(COLUMNS[4])
private val Map<String, Int>.location: Int get() = getValue(COLUMNS[5])
private val Map<String, Int>.portrait: Int get() = getValue(COLUMNS[6])
private val Map<String, Int>.description: Int get() = getValue(COLUMNS[7])
private val Map<String, Int>.history: Int get() = getValue(COLUMNS[8])
private val Map<String, Int>.tags: Int get() = getValue(COLUMNS[9])
companion object {
private val COLUMNS = listOf(
@ -74,6 +80,8 @@ class LexiconParser @Inject constructor(
"Diminutif",
"Sexe",
"Race",
"Statut",
"Localisation",
"Portrait",
"Description",
"Histoire",

View file

@ -11,6 +11,8 @@ data class Lexicon(
val diminutive: String?,
val gender: Gender,
val race: Race,
val status: String?,
val location: String?,
val portrait: List<Uri>,
val description: String?,
val history: String?,

View file

@ -11,9 +11,11 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.lazy.LazyRow
@ -45,6 +47,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@ -70,6 +73,8 @@ data class LexiconDetailUio(
val diminutive: String?,
val gender: Lexicon.Gender,
val race: Lexicon.Race,
val status: String?,
val location: String?,
val portrait: List<Uri>,
val description: String?,
val history: String?,
@ -86,6 +91,8 @@ data class AnnotatedLexiconDetailUio(
val gender: AnnotatedString,
val race: AnnotatedString,
val portrait: List<Uri>,
val status: AnnotatedString?,
val location: AnnotatedString?,
val description: AnnotatedString?,
val history: AnnotatedString?,
val tags: AnnotatedString?
@ -130,6 +137,12 @@ fun LexiconDetailUio.annotate(): AnnotatedLexiconDetailUio {
description = description?.let { description ->
highlightRegex.annotatedString(input = description, spanStyle = highlight)
},
status = status?.let { status ->
highlightRegex.annotatedString(input = status, spanStyle = highlight)
},
location = location?.let { location ->
highlightRegex.annotatedString(input = location, spanStyle = highlight)
},
history = history?.let { history ->
highlightRegex.annotatedString(input = history, spanStyle = highlight)
},
@ -170,6 +183,9 @@ private fun LexiconDetailContent(
val colorScheme = MaterialTheme.colorScheme
val typography = MaterialTheme.typography
val annotatedItem = item.value.annotate()
val backgroundUri = remember(annotatedItem) {
annotatedItem.portrait.firstOrNull()
}
Scaffold(
modifier = modifier,
@ -203,7 +219,7 @@ private fun LexiconDetailContent(
Surface(
modifier = Modifier.padding(paddingValues = paddingValues),
) {
annotatedItem.portrait.firstOrNull()?.let { uri ->
backgroundUri?.let { uri ->
BackgroundImage(
modifier = Modifier
.fillMaxWidth()
@ -216,7 +232,10 @@ private fun LexiconDetailContent(
modifier = Modifier
.verticalScroll(state)
.padding(
top = MaterialTheme.lexicon.dimens.detailPadding,
top = when (backgroundUri) {
null -> 16.dp
else -> MaterialTheme.lexicon.dimens.detailPadding
},
bottom = 16.dp,
),
verticalArrangement = Arrangement.spacedBy(space = 4.dp),
@ -258,6 +277,62 @@ private fun LexiconDetailContent(
)
}
if (annotatedItem.status != null || annotatedItem.location != null) {
Spacer(modifier = Modifier.height(8.dp))
}
annotatedItem.status?.let {
Row(
modifier = Modifier.padding(start = 16.dp, end = 16.dp),
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
Text(
modifier = Modifier.alignByBaseline(),
style = typography.bodyMedium,
fontWeight = FontWeight.Bold,
text = stringResource(id = R.string.detail_status),
)
Text(
modifier = Modifier.alignByBaseline(),
style = remember {
typography.bodyMedium.copy(
shadow = Shadow(
color = colorScheme.surface.copy(alpha = 0.5f),
offset = Offset(x = 1f, y = 1f),
)
)
},
text = it,
)
}
}
annotatedItem.location?.let {
Row(
modifier = Modifier.padding(start = 16.dp, end = 16.dp),
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
Text(
modifier = Modifier.alignByBaseline(),
style = typography.bodyMedium,
fontWeight = FontWeight.Bold,
text = stringResource(id = R.string.detail_location),
)
Text(
modifier = Modifier.alignByBaseline(),
style = remember {
typography.bodyMedium.copy(
shadow = Shadow(
color = colorScheme.surface.copy(alpha = 0.5f),
offset = Offset(x = 1f, y = 1f),
)
)
},
text = it,
)
}
}
annotatedItem.description?.let {
Text(
modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp),
@ -359,6 +434,8 @@ private fun LexiconDetailPreview() {
diminutive = "./ Bru",
gender = Lexicon.Gender.FEMALE,
race = Lexicon.Race.HALF_ORC,
status = "Vivante",
location = "Manoir Durst",
portrait = listOf(
Uri.parse("https://cdnb.artstation.com/p/assets/images/images/003/024/889/large/bayard-wu-0716.jpg?1468642855"),
),

View file

@ -30,6 +30,8 @@ class LexiconDetailViewModel @Inject constructor(
gender = source.gender,
race = source.race,
portrait = source.portrait,
status = source.status,
location = source.location,
description = source.description,
history = source.history,
tags = source.tags,

View file

@ -125,7 +125,7 @@ private fun LexiconScreenContent(
state = lazyColumnState,
contentPadding = MaterialTheme.lexicon.dimens.itemListPadding,
) {
items(count = 6) {
items(count = 16) {
LexiconItem(
modifier = Modifier.cell(),
item = LexiconItemUio.preview(placeholder = true),

View file

@ -38,6 +38,7 @@ import com.pixelized.rplexicon.utilitary.extentions.annotatedString
import com.pixelized.rplexicon.utilitary.extentions.finderRegex
import com.pixelized.rplexicon.utilitary.extentions.foldAll
import com.pixelized.rplexicon.utilitary.extentions.highlightRegex
import com.pixelized.rplexicon.utilitary.extentions.lexicon
@Stable
class SearchItemUio(
@ -46,6 +47,8 @@ class SearchItemUio(
val diminutive: String?,
val gender: Lexicon.Gender,
val race: Lexicon.Race,
val status: String?,
val location: String?,
val description: String?,
val history: String?,
val tags: String?,
@ -64,7 +67,7 @@ class SearchItemUio(
"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 = "",
search: String = " ",
highlightGender: Boolean = false,
highlightRace: Boolean = false,
): SearchItemUio {
@ -74,6 +77,8 @@ class SearchItemUio(
diminutive = diminutive,
gender = gender,
race = race,
status = null,
location = null,
description = description,
history = history,
tags = null,
@ -92,6 +97,8 @@ class AnnotatedSearchItemUio(
val diminutive: AnnotatedString?,
val gender: AnnotatedString,
val race: AnnotatedString,
val status: AnnotatedString?,
val location: AnnotatedString?,
val description: AnnotatedString?,
val history: AnnotatedString?,
val tags: AnnotatedString?,
@ -102,7 +109,9 @@ class AnnotatedSearchItemUio(
private fun SearchItemUio.annotate(): AnnotatedSearchItemUio {
val colorScheme = MaterialTheme.colorScheme
val highlight = remember { SpanStyle(color = colorScheme.primary) }
val trimmedSearch = remember(search) { search.trim() }
val trimmedSearch = remember(search) {
search.split(" ").mapNotNull { it.trim().takeIf { trim -> trim.isNotEmpty() } }
}
val highlightRegex = remember(search) { trimmedSearch.highlightRegex }
val finderRegex = remember(search) { trimmedSearch.finderRegex }
val gender = stringResource(id = gender, short = true)
@ -140,6 +149,12 @@ private fun SearchItemUio.annotate(): AnnotatedSearchItemUio {
}
)
},
status = finderRegex?.foldAll(status)?.let { status ->
highlightRegex?.annotatedString(status, spanStyle = highlight)
},
location = finderRegex?.foldAll(location)?.let { location ->
highlightRegex?.annotatedString(location, spanStyle = highlight)
},
description = finderRegex?.foldAll(description)?.let { description ->
highlightRegex?.annotatedString(description, spanStyle = highlight)
},
@ -160,7 +175,7 @@ fun SearchItem(
item: SearchItemUio,
) {
val typography = MaterialTheme.typography
val colorScheme = MaterialTheme.colorScheme
val colorScheme = MaterialTheme.lexicon.colorScheme
val annotatedItem = item.annotate()
Box(
@ -179,7 +194,8 @@ fun SearchItem(
) {
Text(
modifier = Modifier.alignByBaseline(),
style = remember { typography.bodyLarge.copy(fontWeight = FontWeight.Bold) },
style = typography.bodyLarge,
fontWeight = FontWeight.Bold,
maxLines = 1,
text = annotatedItem.name,
)
@ -199,13 +215,15 @@ fun SearchItem(
) {
Text(
modifier = Modifier.alignByBaseline(),
style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
style = typography.labelMedium,
fontStyle = FontStyle.Italic,
maxLines = 1,
text = annotatedItem.gender
)
Text(
modifier = Modifier.alignByBaseline(),
style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
style = typography.labelMedium,
fontStyle = FontStyle.Italic,
maxLines = 1,
text = annotatedItem.race
)
@ -214,7 +232,7 @@ fun SearchItem(
Column(
modifier = Modifier.drawBehind {
drawLine(
color = colorScheme.primary,
color = colorScheme.base.primary,
start = Offset(0f, 0f),
end = Offset(0f, size.height),
strokeWidth = 2.dp.toPx()
@ -222,16 +240,52 @@ fun SearchItem(
},
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
annotatedItem.status?.let {
Row(
modifier = Modifier.padding(start = 8.dp),
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
Text(
style = typography.labelSmall,
fontWeight = FontWeight.Bold,
text = stringResource(id = R.string.search_item_status),
)
Text(
style = typography.labelSmall,
fontStyle = FontStyle.Italic,
text = it,
)
}
}
annotatedItem.location?.let {
Row(
modifier = Modifier.padding(start = 8.dp),
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
) {
Text(
style = typography.labelSmall,
fontWeight = FontWeight.Bold,
text = stringResource(id = R.string.search_item_location),
)
Text(
style = typography.labelSmall,
fontStyle = FontStyle.Italic,
text = it,
)
}
}
annotatedItem.description?.let {
Column(
modifier = Modifier.padding(start = 8.dp)
) {
Text(
style = remember { typography.labelSmall.copy(fontWeight = FontWeight.Bold) },
style = typography.labelSmall,
fontWeight = FontWeight.Bold,
text = stringResource(id = R.string.search_item_description),
)
Text(
style = typography.labelSmall,
fontStyle = FontStyle.Italic,
text = it,
)
}
@ -241,11 +295,13 @@ fun SearchItem(
modifier = Modifier.padding(start = 8.dp),
) {
Text(
style = remember { typography.labelSmall.copy(fontWeight = FontWeight.Bold) },
style = typography.labelSmall,
fontWeight = FontWeight.Bold,
text = stringResource(id = R.string.search_item_history),
)
Text(
style = typography.labelSmall,
fontStyle = FontStyle.Italic,
text = it,
)
}
@ -255,11 +311,13 @@ fun SearchItem(
modifier = Modifier.padding(start = 8.dp),
) {
Text(
style = remember { typography.labelSmall.copy(fontWeight = FontWeight.Bold) },
style = typography.labelSmall,
fontWeight = FontWeight.Bold,
text = stringResource(id = R.string.search_item_tags),
)
Text(
style = typography.labelSmall,
fontStyle = FontStyle.Italic,
text = it,
)
}

View file

@ -52,15 +52,19 @@ class SearchViewModel @Inject constructor(
data.filter { item ->
val gender = _gender.value?.let { it == item.gender }
val race = _race.value?.let { it == item.race }
val search = _search.value.takeIf { it.isNotEmpty() }?.trim()?.let { search ->
val name = item.name.contains(search, true)
val diminutive = item.diminutive?.contains(search, true) == true
val description = item.description?.contains(search, true) == true
val history = item.history?.contains(search, true) == true
val tag = item.tags?.contains(search, true) == true
name || diminutive || description || history || tag
val search = _search.value.split(" ").mapNotNull {
it.takeIf { it.isNotEmpty() }?.trim()?.let { search ->
val name = item.name.contains(search, true)
val diminutive = item.diminutive?.contains(search, true) == true
val status = item.status?.contains(search, true) == true
val location = item.location?.contains(search, true) == true
val description = item.description?.contains(search, true) == true
val history = item.history?.contains(search, true) == true
val tag = item.tags?.contains(search, true) == true
name || diminutive || status || location || description || history || tag
}
}
(gender == null || gender) && (race == null || race) && (search == null || search)
(gender == null || gender) && (race == null || race) && (search.all { it })
}.map {
it.toSearchUio()
}.sortedBy {
@ -78,6 +82,8 @@ class SearchViewModel @Inject constructor(
diminutive = diminutive?.takeIf { it.isNotBlank() }?.let { "./ $it" },
gender = this.gender,
race = this.race,
status = this.status,
location = this.location,
description = this.description,
history = this.history,
search = search,

View file

@ -73,7 +73,7 @@ fun LocationScreen(
}
}
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class)
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun LocationContent(
modifier: Modifier = Modifier,

View file

@ -76,7 +76,7 @@ fun QuestListScreen(
}
}
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class)
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun QuestListContent(
modifier: Modifier = Modifier,
@ -103,7 +103,7 @@ private fun QuestListContent(
state = lazyColumnState,
contentPadding = MaterialTheme.lexicon.dimens.itemListPadding,
) {
items(count = 2) {
items(count = 4) {
QuestItem(
modifier = Modifier.cell(),
item = QuestItemUio.preview(placeHolder = true),

View file

@ -11,14 +11,20 @@ fun Regex?.foldAll(
this?.findAll(it, startIndex)?.fold("") { acc, item ->
val dummy = acc + when {
previous == null && item.range.first == 0 -> item.value
previous == null && item.range.first != 0 -> "...${item.value}"
previous == null && item.range.first != 0 -> "... ${item.value}"
item.range.first <= (previous?.range?.last ?: 0) + 1 -> item.value
else -> " ... ${item.value}"
}
previous = item
dummy
}
}?.takeIf { it.isNotEmpty() }?.let { "$it..." }
}?.takeIf { it.isNotEmpty() }?.let {
if (it.length == input.length) {
it
} else {
"$it..."
}
}
fun Regex?.annotatedSpan(
input: String,

View file

@ -22,14 +22,25 @@ val String?.highlightRegex: Regex?
}
@Stable
val String?.finderRegex: Regex?
get() = this?.takeIf { it.isNotEmpty() }?.let {
val List<String>.highlightRegex: Regex?
get() = if (isNotEmpty()) {
Regex(
pattern = "\\w*.{0,30}${Regex.escape(it)}\\w*.{0,30}\\w*",
pattern = joinToString(separator = "|") { "(${Regex.escape(it)})" },
option = RegexOption.IGNORE_CASE,
)
} else {
null
}
@Stable
val List<String>.finderRegex: Regex?
get() = if (isNotEmpty()) {
Regex(
pattern = joinToString(separator = "|") { "(\\w*.{0,30}${Regex.escape(it)}\\w*.{0,30}\\w*)" },
option = RegexOption.IGNORE_CASE,
)
} else null
fun String?.toUriOrNull(): Uri? = try {
this?.takeIf { it.isNotBlank() }?.toUri()
} catch (_: Exception) {