Add images support for quest details & quest detail UI clean up.
This commit is contained in:
parent
99d5076168
commit
82ce19ec4d
14 changed files with 209 additions and 259 deletions
|
|
@ -21,5 +21,6 @@ data class QuestEntry(
|
|||
val groupReward: String?,
|
||||
val individualReward: String?,
|
||||
val description: String,
|
||||
val images: List<Uri>,
|
||||
val background: Uri?,
|
||||
)
|
||||
|
|
@ -37,7 +37,6 @@ class QuestRepository @Inject constructor(
|
|||
private suspend fun updateData(data: ValueRange) {
|
||||
val questEntries = questParser.parse(value = data)
|
||||
val questMap = questEntries.groupBy { it.title }
|
||||
|
||||
val quests = questMap.keys.mapIndexed { index, item ->
|
||||
Quest(
|
||||
id = index,
|
||||
|
|
|
|||
|
|
@ -3,79 +3,66 @@ package com.pixelized.rplexicon.repository.parser
|
|||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.model.QuestEntry
|
||||
import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||
import com.pixelized.rplexicon.utilitary.extentions.toUriOrNull
|
||||
import javax.inject.Inject
|
||||
|
||||
class QuestParser @Inject constructor() {
|
||||
|
||||
fun parse(value: ValueRange): List<QuestEntry> {
|
||||
val sheet = value.values.sheet()
|
||||
lateinit var structure: Map<String, Int>
|
||||
|
||||
return sheet?.mapIndexedNotNull { index, item ->
|
||||
when {
|
||||
index == 0 -> {
|
||||
structure = item.checkSheetStructure(model = COLUMNS)
|
||||
null
|
||||
}
|
||||
|
||||
item is List<*> -> {
|
||||
val title = item.getOrNull(structure.title) as? String
|
||||
val subtitle = item.getOrNull(structure.subtitle) as? String?
|
||||
val complete = item.getOrNull(structure.complete) as? String?
|
||||
val questGiver = item.getOrNull(structure.questGiver) as? String?
|
||||
val area = item.getOrNull(structure.area) as? String?
|
||||
val groupReward = item.getOrNull(structure.groupReward) as? String?
|
||||
val individualReward = item.getOrNull(structure.individualReward) as? String?
|
||||
val description = item.getOrNull(structure.description) as? String
|
||||
val background = item.getOrNull(structure.background) as? String?
|
||||
class QuestParser @Inject constructor(
|
||||
private val imageParser: PortraitParser
|
||||
) {
|
||||
fun parse(value: ValueRange): List<QuestEntry> = parserScope {
|
||||
val quest = mutableListOf<QuestEntry>()
|
||||
|
||||
value.forEachRow { index, item ->
|
||||
when (index) {
|
||||
0 -> updateStructure(row = item, columns = COLUMNS)
|
||||
else -> {
|
||||
val title = item.parse(TITLE)
|
||||
val description = item.parse(DESCRIPTION)
|
||||
if (title?.isNotEmpty() == true && description?.isNotEmpty() == true) {
|
||||
QuestEntry(
|
||||
val entry = QuestEntry(
|
||||
sheetIndex = index,
|
||||
title = title,
|
||||
subtitle = subtitle?.takeIf { it.isNotBlank() },
|
||||
complete = complete.equals("TRUE", ignoreCase = true),
|
||||
questGiver = questGiver?.takeIf { it.isNotBlank() },
|
||||
area = area?.takeIf { it.isNotBlank() },
|
||||
groupReward = groupReward?.takeIf { it.isNotBlank() },
|
||||
individualReward = individualReward?.takeIf { it.isNotBlank() },
|
||||
subtitle = item.parse(SUB_TITLE),
|
||||
complete = item.parseBool(COMPLETED) ?: false,
|
||||
questGiver = item.parse(QUEST_GIVER),
|
||||
area = item.parse(AREA),
|
||||
groupReward = item.parse(GROUP_REWARD),
|
||||
individualReward = item.parse(INDIVIDUAL_REWARD),
|
||||
description = description,
|
||||
background = background?.toUriOrNull(),
|
||||
images = imageParser.parse(item.parse(IMAGE)),
|
||||
background = item.parseUri(BACKGROUND),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
quest.add(entry)
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
quest
|
||||
}
|
||||
|
||||
private val Map<String, Int>.title: Int get() = getValue(COLUMNS[0])
|
||||
private val Map<String, Int>.subtitle: Int get() = getValue(COLUMNS[1])
|
||||
private val Map<String, Int>.complete: Int get() = getValue(COLUMNS[2])
|
||||
private val Map<String, Int>.questGiver: Int get() = getValue(COLUMNS[3])
|
||||
private val Map<String, Int>.area: Int get() = getValue(COLUMNS[4])
|
||||
private val Map<String, Int>.groupReward: Int get() = getValue(COLUMNS[5])
|
||||
private val Map<String, Int>.individualReward: Int get() = getValue(COLUMNS[6])
|
||||
private val Map<String, Int>.description: Int get() = getValue(COLUMNS[7])
|
||||
private val Map<String, Int>.background: Int get() = getValue(COLUMNS[8])
|
||||
|
||||
companion object {
|
||||
private const val TITLE = "Titre"
|
||||
private const val SUB_TITLE = "Sous Titre"
|
||||
private const val COMPLETED = "Compléter"
|
||||
private const val QUEST_GIVER = "Commanditaire"
|
||||
private const val AREA = "Lieu"
|
||||
private const val GROUP_REWARD = "Récompense de groupe"
|
||||
private const val INDIVIDUAL_REWARD = "Récompense individuelle"
|
||||
private const val DESCRIPTION = "Description"
|
||||
private const val IMAGE = "Image"
|
||||
private const val BACKGROUND = "fond" // TODO
|
||||
|
||||
private val COLUMNS = listOf(
|
||||
"Titre",
|
||||
"Sous Titre",
|
||||
"Compléter",
|
||||
"Commanditaire",
|
||||
"Lieu",
|
||||
"Récompense de groupe",
|
||||
"Récompense individuelle",
|
||||
"Description",
|
||||
"fond"
|
||||
TITLE,
|
||||
SUB_TITLE,
|
||||
COMPLETED,
|
||||
QUEST_GIVER,
|
||||
AREA,
|
||||
GROUP_REWARD,
|
||||
INDIVIDUAL_REWARD,
|
||||
DESCRIPTION,
|
||||
IMAGE,
|
||||
BACKGROUND,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package com.pixelized.rplexicon.repository.parser
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||
|
|
@ -34,4 +36,10 @@ class SheetParserScope<T> {
|
|||
|
||||
fun List<*>.parseInt(column: String): Int? =
|
||||
parse(column)?.toIntOrNull()
|
||||
|
||||
fun List<*>.parseBool(column: String): Boolean? =
|
||||
parse(column)?.equals("TRUE", ignoreCase = true)
|
||||
|
||||
fun List<*>.parseUri(column: String): Uri? =
|
||||
parse(column)?.takeIf { it.isNotBlank() }?.toUri()
|
||||
}
|
||||
|
|
@ -368,16 +368,6 @@ private fun LocationContent(
|
|||
style = MaterialTheme.typography.headlineSmall,
|
||||
text = item.value.name,
|
||||
)
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.height(24.dp)
|
||||
.graphicsLayer { rotationZ = 180f },
|
||||
painter = painterResource(id = R.drawable.art_divider_1),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
alignment = Alignment.Center,
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalPager(
|
||||
|
|
|
|||
|
|
@ -2,18 +2,19 @@ package com.pixelized.rplexicon.ui.screens.quest.detail
|
|||
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
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.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
|
|
@ -28,18 +29,14 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
|
@ -49,16 +46,19 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.composable.AsyncImage
|
||||
import com.pixelized.rplexicon.ui.composable.BackgroundImage
|
||||
import com.pixelized.rplexicon.ui.composable.FullScreenImageHandler
|
||||
import com.pixelized.rplexicon.ui.composable.FullScreenImageViewModel
|
||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconDetail
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLocationDetail
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.LOS_FULL
|
||||
import com.pixelized.rplexicon.utilitary.LOS_HOLLOW
|
||||
import com.pixelized.rplexicon.utilitary.extentions.annotateWithDropCap
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
import com.pixelized.rplexicon.utilitary.extentions.scrollOffset
|
||||
import java.lang.Integer.min
|
||||
|
||||
@Stable
|
||||
data class QuestDetailUio(
|
||||
|
|
@ -70,82 +70,22 @@ data class QuestDetailUio(
|
|||
) {
|
||||
@Stable
|
||||
data class QuestStep(
|
||||
val subtitle: String?,
|
||||
val subtitle: String? = null,
|
||||
val giverId: Int? = null,
|
||||
val giver: String?,
|
||||
val giver: String? = null,
|
||||
val placeId: Int? = null,
|
||||
val place: String?,
|
||||
val globalReward: String?,
|
||||
val individualReward: String?,
|
||||
val place: String? = null,
|
||||
val globalReward: String? = null,
|
||||
val individualReward: String? = null,
|
||||
val images: List<Uri> = emptyList(),
|
||||
val description: String,
|
||||
)
|
||||
}
|
||||
|
||||
@Stable
|
||||
data class AnnotatedQuestDetailUio(
|
||||
val title: String,
|
||||
val completed: Boolean,
|
||||
val background: Uri?,
|
||||
val steps: List<AnnotatedQuestStep>,
|
||||
) {
|
||||
@Stable
|
||||
data class AnnotatedQuestStep(
|
||||
val subtitle: String?,
|
||||
val giverId: Int?,
|
||||
val giver: String?,
|
||||
val placeId: Int?,
|
||||
val place: String?,
|
||||
val individualReward: String?,
|
||||
val globalReward: String?,
|
||||
val description: AnnotatedString,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
private fun QuestDetailUio.annotate(): AnnotatedQuestDetailUio {
|
||||
val annotatedSteps = steps.map { it.annotate() }
|
||||
return remember {
|
||||
AnnotatedQuestDetailUio(
|
||||
completed = completed,
|
||||
title = title,
|
||||
background = background,
|
||||
steps = annotatedSteps,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Stable
|
||||
private fun QuestDetailUio.QuestStep.annotate(): AnnotatedQuestDetailUio.AnnotatedQuestStep {
|
||||
val typography = MaterialTheme.lexicon.typography
|
||||
|
||||
return remember {
|
||||
AnnotatedQuestDetailUio.AnnotatedQuestStep(
|
||||
subtitle = subtitle,
|
||||
giverId = giverId,
|
||||
giver = giver,
|
||||
placeId = placeId,
|
||||
place = place,
|
||||
globalReward = globalReward,
|
||||
individualReward = individualReward,
|
||||
description = AnnotatedString(
|
||||
text = description,
|
||||
spanStyles = listOf(
|
||||
AnnotatedString.Range(
|
||||
item = typography.bodyDropCapSpan,
|
||||
start = 0,
|
||||
end = min(1, description.length),
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun QuestDetailScreen(
|
||||
viewModel: QuestDetailViewModel = hiltViewModel(),
|
||||
imageViewModel: FullScreenImageViewModel = hiltViewModel(),
|
||||
) {
|
||||
val screen = LocalScreenNavHost.current
|
||||
|
||||
|
|
@ -156,6 +96,11 @@ fun QuestDetailScreen(
|
|||
onBack = { screen.popBackStack() },
|
||||
onGiver = { screen.navigateToLexiconDetail(id = it) },
|
||||
onLocation = { screen.navigateToLocationDetail(id = it) },
|
||||
onImage = { imageViewModel.showDetail(it) }
|
||||
)
|
||||
|
||||
FullScreenImageHandler(
|
||||
viewModel = imageViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -169,8 +114,9 @@ private fun QuestDetailContent(
|
|||
onBack: () -> Unit,
|
||||
onGiver: (Int) -> Unit,
|
||||
onLocation: (Int) -> Unit,
|
||||
onImage: (Uri) -> Unit,
|
||||
) {
|
||||
val annotatedQuest = item.value.annotate()
|
||||
val quest = item.value
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
|
|
@ -203,9 +149,9 @@ private fun QuestDetailContent(
|
|||
) {
|
||||
BackgroundImage(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
model = annotatedQuest.background,
|
||||
model = quest.background,
|
||||
)
|
||||
if (annotatedQuest.completed) {
|
||||
if (quest.completed) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
|
|
@ -213,7 +159,7 @@ private fun QuestDetailContent(
|
|||
.rotate(degrees = 12f),
|
||||
style = MaterialTheme.lexicon.typography.stamp,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.35f),
|
||||
text = "Completed",
|
||||
text = stringResource(id = R.string.quest_detail_completed),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -221,80 +167,55 @@ private fun QuestDetailContent(
|
|||
modifier = Modifier
|
||||
.verticalScroll(state)
|
||||
.padding(
|
||||
top = when (annotatedQuest.background) {
|
||||
null -> 16.dp
|
||||
top = when {
|
||||
quest.background == null && quest.completed -> 96.dp
|
||||
quest.background == null -> 16.dp
|
||||
else -> MaterialTheme.lexicon.dimens.detailPadding
|
||||
},
|
||||
end = 16.dp,
|
||||
bottom = 16.dp,
|
||||
start = 16.dp,
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
text = annotatedQuest.title,
|
||||
)
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.height(24.dp)
|
||||
.graphicsLayer { rotationZ = 180f }
|
||||
.align(Alignment.CenterHorizontally),
|
||||
painter = painterResource(id = R.drawable.art_divider_1),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
alignment = Alignment.Center,
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 16.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
text = quest.title.annotateWithDropCap(
|
||||
style = MaterialTheme.lexicon.typography.headlineLargeDropCap,
|
||||
),
|
||||
)
|
||||
|
||||
annotatedQuest.steps.forEach { quest ->
|
||||
quest.steps.forEach { quest ->
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
quest.subtitle?.let { subtitle ->
|
||||
Row(
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 64.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.graphicsLayer { rotationY = 180f },
|
||||
painter = painterResource(id = R.drawable.art_clip_1),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
alignment = Alignment.Center,
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(weight = 1f, fill = false)
|
||||
.padding(horizontal = 8.dp),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 3,
|
||||
text = subtitle,
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.art_clip_1),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
alignment = Alignment.Center,
|
||||
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(top = 16.dp),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 3,
|
||||
text = subtitle.annotateWithDropCap(
|
||||
style = MaterialTheme.lexicon.typography.titleLargeDropCap,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
quest.giver?.let {
|
||||
Column(
|
||||
modifier = Modifier.clickable(
|
||||
enabled = quest.giverId != null,
|
||||
onClick = { quest.giverId?.let { onGiver(it) } }
|
||||
)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
enabled = quest.giverId != null,
|
||||
onClick = { quest.giverId?.let { onGiver(it) } }
|
||||
)
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
|
|
@ -310,12 +231,16 @@ private fun QuestDetailContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
quest.place?.let {
|
||||
Column(
|
||||
modifier = Modifier.clickable(
|
||||
enabled = quest.placeId != null,
|
||||
onClick = { quest.placeId?.let { onLocation(it) } }
|
||||
)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
enabled = quest.placeId != null,
|
||||
onClick = { quest.placeId?.let { onLocation(it) } }
|
||||
)
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
|
|
@ -331,8 +256,11 @@ private fun QuestDetailContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
quest.globalReward?.let {
|
||||
Column {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
|
|
@ -340,12 +268,15 @@ private fun QuestDetailContent(
|
|||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = it,
|
||||
text = "$LOS_HOLLOW $it",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
quest.individualReward?.let {
|
||||
Column {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
|
|
@ -353,15 +284,35 @@ private fun QuestDetailContent(
|
|||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = it,
|
||||
text = "$LOS_HOLLOW $it",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = quest.description,
|
||||
text = quest.description.annotateWithDropCap(
|
||||
style = MaterialTheme.lexicon.typography.bodyMediumDropCap,
|
||||
),
|
||||
)
|
||||
|
||||
if (quest.images.isNotEmpty()) {
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
items(items = quest.images) {
|
||||
AsyncImage(
|
||||
modifier = Modifier
|
||||
.clickable { onImage(it) }
|
||||
.height(height = 160.dp),
|
||||
contentScale = ContentScale.FillHeight,
|
||||
model = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -383,6 +334,7 @@ private fun QuestDetailPreview(
|
|||
onBack = { },
|
||||
onGiver = { },
|
||||
onLocation = { },
|
||||
onImage = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -402,15 +354,11 @@ private class QuestDetailPreviewProvider : PreviewParameterProvider<State<QuestD
|
|||
giver = "Sergent d'arme",
|
||||
place = "DaggerFord",
|
||||
individualReward = "5po",
|
||||
globalReward = null,
|
||||
description = "Des nobles participant aux festivités de DaggerFord aurait entendu des loups dans la forêt proche. Sur ordre du baron, cette forêt doit être fouillée bien que depuis 300 ans, aucun loup n'y ait été vu.",
|
||||
),
|
||||
QuestDetailUio.QuestStep(
|
||||
subtitle = "Partie 2",
|
||||
giver = "Sergent d'arme",
|
||||
place = "DaggerFord",
|
||||
individualReward = "5po. Bonus de 1po par loup.",
|
||||
globalReward = null,
|
||||
description = "Nous devons rapporter la dépouille d'un loup pour prouver au sergent que nous avons tué ces bêtes.",
|
||||
),
|
||||
)
|
||||
|
|
@ -424,11 +372,7 @@ private class QuestDetailPreviewProvider : PreviewParameterProvider<State<QuestD
|
|||
title = "Les enfants de la caravanes",
|
||||
steps = listOf(
|
||||
QuestDetailUio.QuestStep(
|
||||
subtitle = null,
|
||||
giver = null,
|
||||
place = null,
|
||||
individualReward = "Pouvoir se regarder dans une glace",
|
||||
globalReward = null,
|
||||
globalReward = "Pouvoir se regarder dans une glace",
|
||||
description = "Une meute de lycan a massacré une caravane marchande quittant DaggerFord. Leur dessin n'est pas encore clair, mais ils ont enlevé les enfants. Nous les pourchassons afin de les sauver.",
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class QuestDetailViewModel @Inject constructor(
|
|||
globalReward = entry.groupReward,
|
||||
individualReward = entry.individualReward,
|
||||
description = entry.description,
|
||||
images = entry.images,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ import com.pixelized.rplexicon.repository.data.lexicon.QuestRepository
|
|||
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
|
|
@ -30,25 +32,34 @@ class QuestListViewModel @Inject constructor(
|
|||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
launch {
|
||||
launch(Dispatchers.IO) {
|
||||
repository.data.collect { items ->
|
||||
_items.value = items.map { item ->
|
||||
QuestItemUio(
|
||||
id = item.id,
|
||||
title = item.title,
|
||||
complete = item.entries.all { it.complete },
|
||||
)
|
||||
}.sortedBy { it.title }
|
||||
val quest = items
|
||||
.map { item ->
|
||||
QuestItemUio(
|
||||
id = item.id,
|
||||
title = item.title,
|
||||
complete = item.entries.all { it.complete },
|
||||
)
|
||||
}
|
||||
.sortedBy { it.title }
|
||||
.sortedBy { it.complete }
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
_items.value = quest
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
launch(Dispatchers.IO) {
|
||||
update(force = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun update(force: Boolean) {
|
||||
_isLoading.value = true
|
||||
withContext(context = Dispatchers.Main) {
|
||||
_isLoading.value = true
|
||||
}
|
||||
try {
|
||||
if (force || repository.lastSuccessFullUpdate.shouldUpdate()) {
|
||||
repository.fetchQuests()
|
||||
|
|
@ -64,9 +75,11 @@ class QuestListViewModel @Inject constructor(
|
|||
Log.e(TAG, exception.message, exception)
|
||||
_error.emit(FetchErrorUio.Default)
|
||||
}
|
||||
// clean the laoding state
|
||||
// clean the loading state
|
||||
finally {
|
||||
_isLoading.value = false
|
||||
withContext(context = Dispatchers.Main) {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ data class LexiconDimens(
|
|||
fun lexiconDimen(
|
||||
density: Density,
|
||||
itemHeight: Dp = 52.dp,
|
||||
detailPadding: Dp = 248.dp,
|
||||
detailPadding: Dp = 320.dp,
|
||||
itemListPadding: PaddingValues = PaddingValues(
|
||||
top = 8.dp,
|
||||
bottom = 8.dp + 16.dp + 56.dp + 16.dp,
|
||||
|
|
|
|||
|
|
@ -28,18 +28,43 @@ class LexiconTypography(
|
|||
val stamp: TextStyle = base.headlineLarge.copy(
|
||||
fontFamily = stampFontFamily,
|
||||
),
|
||||
@Deprecated("")
|
||||
val bodyDropCap: TextStyle = base.headlineLarge.copy(
|
||||
fontFamily = zallFontFamily,
|
||||
baselineShift = BaselineShift(-0.1f),
|
||||
letterSpacing = (-3).sp
|
||||
),
|
||||
@Deprecated("")
|
||||
val titleDropCap: TextStyle = base.displayMedium.copy(
|
||||
fontFamily = zallFontFamily,
|
||||
baselineShift = BaselineShift(-0.1f),
|
||||
letterSpacing = (-4).sp
|
||||
),
|
||||
@Deprecated("")
|
||||
val bodyDropCapSpan: SpanStyle = bodyDropCap.toSpanStyle(),
|
||||
@Deprecated("")
|
||||
val titleDropCapSpan: SpanStyle = titleDropCap.toSpanStyle(),
|
||||
|
||||
val bodyMediumDropCap: SpanStyle = base.bodyMedium.toDropCapSpan(
|
||||
sizeRatio = 1.8f,
|
||||
),
|
||||
|
||||
val titleLargeDropCap: SpanStyle = base.titleLarge.toDropCapSpan(
|
||||
sizeRatio = 1.4f,
|
||||
),
|
||||
|
||||
val headlineLargeDropCap: SpanStyle = base.headlineLarge.toDropCapSpan(
|
||||
sizeRatio = 1.2f,
|
||||
),
|
||||
)
|
||||
|
||||
private fun TextStyle.toDropCapSpan(
|
||||
sizeRatio : Float,
|
||||
): SpanStyle {
|
||||
return copy(
|
||||
fontFamily = zallFontFamily,
|
||||
fontSize = fontSize * sizeRatio,
|
||||
).toSpanStyle()
|
||||
}
|
||||
|
||||
fun lexiconTypography() = LexiconTypography()
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="1280"
|
||||
android:viewportHeight="425">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M961.5,1.9c-9.1,3.3 -16.9,7.2 -25,12.6 -23.2,15.4 -34.6,32.5 -44.4,67 -1.7,6.1 -3.8,12.7 -4.6,14.8 -0.8,2 -1.5,6.6 -1.5,10.1 0,12.6 8.9,24.2 24.7,32.3 9.9,5.1 18.6,7.3 29.4,7.3 11.1,-0 16.6,-1.9 21.7,-7.4 3.4,-3.7 4.1,-5.4 5.2,-11.3 0.9,-5.5 0.9,-8 -0.1,-11.2 -2.7,-9.2 -8.7,-10.1 -28.1,-4.3 -17.4,5.3 -22.7,6 -27.7,3.8 -5,-2.3 -7.1,-6.2 -7.8,-14.4 -1.1,-13.4 3.8,-32.3 12.5,-48.2 5.5,-10 20.2,-25.5 29.4,-30.9 17.4,-10.2 35.8,-13.2 58.2,-9.6 47.7,7.7 88.8,46.1 102.8,96 4.1,14.3 4.7,17.4 5.9,30 2.4,25 -1.3,47.7 -10.9,67 -4.8,9.7 -11,18.7 -29.4,42.9 -40.1,52.4 -60.3,73.6 -96,100.4 -29.1,21.8 -49,32.2 -61.8,32.2 -11.3,-0 -24,-3.4 -73.2,-19.6 -60.6,-19.9 -100.3,-34.7 -154.8,-57.7 -29.3,-12.3 -54.7,-24.4 -108,-51.3 -23.9,-12.1 -52,-25.7 -62.5,-30.3 -58.4,-25.8 -108.4,-38.8 -165.8,-43.2 -20.2,-1.5 -70.9,-0.7 -94.7,1.6 -17.2,1.6 -21.2,2.4 -47.5,8.4 -26.1,5.9 -48.3,7.8 -54.5,4.6 -3.6,-1.8 -3.9,-4.2 -0.7,-5.5 51.5,-21.1 65.9,-26.1 94.1,-32.6 50.5,-11.6 88.6,-16.3 137.3,-17.1 78.9,-1.3 144.1,8 230.3,32.9 33.6,9.7 61.3,20.6 115.7,45.6 23.5,10.8 72.9,31.3 103.1,42.8 12,4.5 39.1,12.5 50.7,14.9 13.3,2.7 30.1,3.8 42.5,2.5 6.3,-0.6 17.6,-1.5 25,-2 22.4,-1.7 35.9,-6.1 49.3,-16.3 11.3,-8.6 18.1,-19.1 17.5,-27 -0.8,-11.1 -14.9,-7.2 -36.8,10.1 -11.8,9.3 -20,13.8 -34.5,18.5l-13,4.2 -20,-0.1c-18.1,-0 -21.1,-0.3 -31.5,-2.7 -27.6,-6.5 -54.9,-16.3 -107.1,-38.5 -16.7,-7.2 -38.5,-16.4 -48.4,-20.5 -9.9,-4.2 -22.9,-9.9 -28.9,-12.7 -40.8,-19.1 -70.8,-29.6 -119.1,-41.5 -23,-5.7 -43.3,-9.6 -70.2,-13.5 -39,-5.7 -56,-7.1 -97.4,-7.7 -76.2,-1.2 -122.4,4.5 -194.4,23.7 -30.9,8.3 -33.1,9.1 -63.5,23.5 -27.6,13.1 -32.2,14.5 -50.2,15.4 -13.6,0.7 -15.1,1 -21,3.9 -11.4,5.6 -16.6,12.1 -25.2,31.4 -4.1,9.2 -10.4,15.9 -17.2,18.2 -5.2,1.7 -13.7,8.4 -27.6,21.3 -12.5,11.6 -12.2,11.1 -9.5,16.4 3,5.7 6.3,6.3 12.1,2.1 12.3,-8.8 40.8,-24.2 44.8,-24.2 3.2,-0 5.9,2 7.4,5.5 1.5,3.6 7.6,8.4 14.7,11.5 7.2,3.2 21.3,5.2 36.2,5.4 13.1,0.1 14.3,-0.1 18.9,-2.4 6.1,-3.1 8.4,-6.1 9.2,-12.1 0.5,-4 0.3,-5 -1.5,-6.8 -2.8,-2.8 -4.6,-2.4 -19.7,3.5 -20,7.9 -28,8.8 -34.1,4.1 -1.4,-1.1 -5.2,-3.3 -8.4,-4.8 -14.2,-6.7 -10.4,-15.5 9.1,-21 4,-1.2 15.7,-6 25.9,-10.9 35.7,-16.8 48.2,-21 56.8,-18.9 11.2,2.7 27,11.6 50.3,28.4 4.7,3.4 14.4,9.5 21.5,13.7 23.9,13.8 101.5,52.4 143.5,71.3 22.8,10.3 48.1,21.8 56.1,25.6 28,13.2 71.3,28.2 129.4,44.8 77.6,22.1 133.6,30.3 195.5,28.7 23.7,-0.6 50.9,-3.1 69.4,-6.2 16.7,-2.8 28.1,-6.1 46.1,-13.2 25.4,-9.9 28,-10.6 39.5,-9.9 5.2,0.3 15.6,2 23,3.6 16.8,3.8 23.5,4.7 42.9,6.1 19.9,1.4 62.4,1.4 77.1,0.1 48.2,-4.5 93.9,-17.2 125.4,-34.8 24.4,-13.6 39.2,-25.4 69.6,-55.3 10.6,-10.4 16.4,-27.4 15.8,-45.8 -0.2,-9.1 -0.7,-11.5 -2.8,-15.6 -3.2,-6.3 -8.2,-9.1 -16.1,-9.1 -13.1,-0 -15.3,5.6 -10.3,26.8 3.6,15.2 3.9,28.4 0.6,34.5 -3.1,5.9 -20.7,23.3 -32.2,31.8 -12.4,9.3 -37,24 -54.5,32.5 -21.1,10.4 -39.2,15.8 -67,20.1 -13.8,2.1 -18.3,2.3 -54.5,2.2 -32.9,-0 -43.4,-0.4 -63,-2.2 -32.4,-3.1 -41.8,-4.3 -40,-5.4 53.4,-31.2 78.9,-50.2 105.1,-77.9 23.9,-25.2 38.3,-43.5 52.9,-67 20.7,-33.3 29,-61.3 27.7,-93.4 -2,-48.8 -25.2,-92.7 -65.7,-124.4 -6.3,-5 -22.7,-13.1 -31.5,-15.6 -21.3,-6.1 -55.1,-8.5 -66,-4.6zM351.5,190.8c32,2 64.4,9.2 115,25.4 37.4,12 60,22.2 110,49.8 43.3,23.9 171.4,80.5 193.8,85.5 4,0.9 20.7,6.2 37.2,11.7 29.9,10 52.5,17 74.8,23 6.4,1.7 11.7,3.5 11.7,3.9 0,0.5 -1.7,1.1 -3.7,1.4 -2.1,0.4 -10.2,2.6 -18,5 -34.9,10.8 -64.3,14.6 -113.4,14.5 -30.8,-0 -43.5,-0.6 -76.9,-4.1 -26.7,-2.7 -42,-5.3 -59,-9.9 -80,-21.6 -116,-32.8 -136,-42.3 -6.3,-3 -15.5,-6.7 -20.5,-8.2 -24.7,-7.5 -94.4,-40.7 -186,-88.4 -24.4,-12.8 -34.9,-18.8 -40.2,-23.2 -4,-3.2 -7.9,-5.9 -8.6,-5.9 -2.5,-0 -40.7,-21.7 -40.7,-23.1 0,-0.4 3,-1.2 6.8,-1.9 3.7,-0.6 14.4,-3.1 23.7,-5.5 20.4,-5.3 33.8,-7.4 54.5,-8.5 18.8,-0.9 53.7,-0.6 75.5,0.8zM143.8,202.7c3.5,0.9 5.9,1.9 5.5,2.3 -0.4,0.4 -6,2.4 -12.3,4.5 -6.2,2 -14.5,5.2 -18.2,7.1 -3.7,1.9 -10,4.7 -14,6.3 -4,1.6 -13.6,5.9 -21.3,9.5 -20,9.4 -21.8,8.9 -5.8,-1.6 11.1,-7.3 14.8,-9.5 32.3,-19.8 6.9,-4.1 13.9,-8.2 15.5,-9.2 3.4,-2 7.6,-1.8 18.3,0.9zM96.5,203c-0.4,0.6 -2.7,2.1 -5.3,3.4 -2.6,1.3 -5.6,3.4 -6.7,4.6 -2.4,2.6 -7.2,5.3 -8.7,4.8 -1.2,-0.4 9.9,-9.1 15.4,-12.1 3.6,-1.9 6.3,-2.3 5.3,-0.7z"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -78,6 +78,7 @@
|
|||
<string name="search_item_tags">Mots clés :</string>
|
||||
|
||||
<string name="quest_detail_title">Détails de quête</string>
|
||||
<string name="quest_detail_completed">Complétée</string>
|
||||
<string name="quest_detail_giver">Commanditaire :</string>
|
||||
<string name="quest_detail_area">Lieu :</string>
|
||||
<string name="quest_detail_individual_reward">Récompense individuelle :</string>
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@
|
|||
<string name="search_item_tags">Tags:</string>
|
||||
|
||||
<string name="quest_detail_title">Quest details</string>
|
||||
<string name="quest_detail_completed">Completed</string>
|
||||
<string name="quest_detail_giver">Quest giver:</string>
|
||||
<string name="quest_detail_area">Area:</string>
|
||||
<string name="quest_detail_individual_reward">Individual reward:</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue