Add background image to quest.

This commit is contained in:
Thomas Andres Gomez 2023-08-01 20:52:45 +02:00
parent 331fbb87e5
commit 6a326ad1a8
12 changed files with 321 additions and 158 deletions

View file

@ -1,11 +1,13 @@
package com.pixelized.rplexicon.model package com.pixelized.rplexicon.model
import android.net.Uri
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
@Stable @Stable
data class Quest( data class Quest(
val id: Int, val id: Int,
val title: String, val title: String,
val background: Uri?,
val entries: List<QuestEntry>, val entries: List<QuestEntry>,
) )
@ -20,4 +22,5 @@ data class QuestEntry(
val groupReward: String?, val groupReward: String?,
val individualReward: String?, val individualReward: String?,
val description: String, val description: String,
val background: Uri?,
) )

View file

@ -13,6 +13,7 @@ import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure
import com.pixelized.rplexicon.utilitary.extentions.sheet import com.pixelized.rplexicon.utilitary.extentions.sheet
import com.pixelized.rplexicon.utilitary.extentions.toUriOrNull
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -135,12 +136,6 @@ class LexiconRepository @Inject constructor(
} }
} }
private fun String?.toUriOrNull(): Uri? = try {
this?.takeIf { it.isNotBlank() }?.toUri()
} catch (_: Exception) {
null
}
private val Map<String, Int>?.name: Int get() = this?.getValue(Sheet.NAME) ?: 0 private val Map<String, Int>?.name: Int get() = this?.getValue(Sheet.NAME) ?: 0
private val Map<String, Int>?.diminutive: Int get() = this?.getValue(Sheet.DIMINUTIVE) ?: 1 private val Map<String, Int>?.diminutive: Int get() = this?.getValue(Sheet.DIMINUTIVE) ?: 1
private val Map<String, Int>?.gender: Int get() = this?.getValue(Sheet.GENDER) ?: 2 private val Map<String, Int>?.gender: Int get() = this?.getValue(Sheet.GENDER) ?: 2

View file

@ -12,6 +12,7 @@ import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure
import com.pixelized.rplexicon.utilitary.extentions.sheet import com.pixelized.rplexicon.utilitary.extentions.sheet
import com.pixelized.rplexicon.utilitary.extentions.toUriOrNull
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -82,6 +83,7 @@ class QuestRepository @Inject constructor(
Quest( Quest(
id = index, id = index,
title = item, title = item,
background = questMap[item]?.mapNotNull { it.background }?.randomOrNull(),
entries = questMap[item] ?: emptyList(), entries = questMap[item] ?: emptyList(),
) )
} }
@ -96,24 +98,26 @@ class QuestRepository @Inject constructor(
): QuestEntry? { ): QuestEntry? {
val title = row?.getOrNull(sheetStructure.title) as? String val title = row?.getOrNull(sheetStructure.title) as? String
val subtitle = row?.getOrNull(sheetStructure.subtitle) as? String? val subtitle = row?.getOrNull(sheetStructure.subtitle) as? String?
val complete = row?.getOrNull(sheetStructure.complete) as? Boolean? ?: false val complete = row?.getOrNull(sheetStructure.complete) as? String?
val questGiver = row?.getOrNull(sheetStructure.questGiver) as? String? val questGiver = row?.getOrNull(sheetStructure.questGiver) as? String?
val area = row?.getOrNull(sheetStructure.area) as? String? val area = row?.getOrNull(sheetStructure.area) as? String?
val groupReward = row?.getOrNull(sheetStructure.groupReward) as? String? val groupReward = row?.getOrNull(sheetStructure.groupReward) as? String?
val individualReward = row?.getOrNull(sheetStructure.individualReward) as? String? val individualReward = row?.getOrNull(sheetStructure.individualReward) as? String?
val description = row?.getOrNull(sheetStructure.description) as? String val description = row?.getOrNull(sheetStructure.description) as? String
val background = row?.getOrNull(sheetStructure.background) as? String?
return if (title?.isNotEmpty() == true && description?.isNotEmpty() == true) { return if (title?.isNotEmpty() == true && description?.isNotEmpty() == true) {
QuestEntry( QuestEntry(
sheetIndex = sheetIndex, sheetIndex = sheetIndex,
title = title, title = title,
subtitle = subtitle?.takeIf { it.isNotBlank() }, subtitle = subtitle?.takeIf { it.isNotBlank() },
complete = complete, complete = complete.equals("TRUE", ignoreCase = true),
questGiver = questGiver?.takeIf { it.isNotBlank() }, questGiver = questGiver?.takeIf { it.isNotBlank() },
area = area?.takeIf { it.isNotBlank() }, area = area?.takeIf { it.isNotBlank() },
groupReward = groupReward?.takeIf { it.isNotBlank() }, groupReward = groupReward?.takeIf { it.isNotBlank() },
individualReward = individualReward?.takeIf { it.isNotBlank() }, individualReward = individualReward?.takeIf { it.isNotBlank() },
description = description, description = description,
background = background?.toUriOrNull(),
) )
} else { } else {
null null
@ -128,6 +132,7 @@ class QuestRepository @Inject constructor(
private val Map<String, Int>?.groupReward: Int get() = this?.getValue(Sheet.G_REWARD) ?: 5 private val Map<String, Int>?.groupReward: Int get() = this?.getValue(Sheet.G_REWARD) ?: 5
private val Map<String, Int>?.individualReward: Int get() = this?.getValue(Sheet.I_REWARD) ?: 6 private val Map<String, Int>?.individualReward: Int get() = this?.getValue(Sheet.I_REWARD) ?: 6
private val Map<String, Int>?.description: Int get() = this?.getValue(Sheet.DESCRIPTION) ?: 7 private val Map<String, Int>?.description: Int get() = this?.getValue(Sheet.DESCRIPTION) ?: 7
private val Map<String, Int>?.background: Int get() = this?.getValue(Sheet.BACKGROUND) ?: 8
private object Sheet { private object Sheet {
const val ID = "1sDAay8DjbRYKM39MvEXWs-RuvyxjOFpOfRZLAEWjIUY" const val ID = "1sDAay8DjbRYKM39MvEXWs-RuvyxjOFpOfRZLAEWjIUY"
@ -143,6 +148,7 @@ class QuestRepository @Inject constructor(
"Récompense de groupe", "Récompense de groupe",
"Récompense individuelle", "Récompense individuelle",
"Description", "Description",
"fond"
) )
val TITLE = COLUMNS[0] val TITLE = COLUMNS[0]
val SUBTITLE = COLUMNS[1] val SUBTITLE = COLUMNS[1]
@ -152,5 +158,6 @@ class QuestRepository @Inject constructor(
val G_REWARD = COLUMNS[5] val G_REWARD = COLUMNS[5]
val I_REWARD = COLUMNS[6] val I_REWARD = COLUMNS[6]
val DESCRIPTION = COLUMNS[7] val DESCRIPTION = COLUMNS[7]
val BACKGROUND = COLUMNS[8]
} }
} }

View file

@ -0,0 +1,60 @@
package com.pixelized.rplexicon.ui.composable
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.layout.ContentScale
import com.pixelized.rplexicon.R
import com.skydoves.landscapist.ImageOptions
@Composable
fun BackgroundImage(
modifier: Modifier = Modifier,
alignment: Alignment = Alignment.TopCenter,
contentScale: ContentScale = ContentScale.Crop,
model: () -> Any?,
) {
Box(
modifier = modifier
) {
AsyncImage(
modifier = Modifier.matchParentSize(),
imageOptions = ImageOptions(
alignment = alignment,
contentScale = contentScale,
colorFilter = remember {
ColorFilter.colorMatrix(
ColorMatrix().also { it.setToSaturation(0f) }
)
},
),
imageModel = model,
previewPlaceholder = R.drawable.im_brulkhai,
)
Box(
modifier = Modifier
.matchParentSize()
.background(brush = rememberBackgroundGradient())
)
}
}
@Composable
private fun rememberBackgroundGradient(): Brush {
val colorScheme = MaterialTheme.colorScheme
return remember {
Brush.verticalGradient(
colors = listOf(
colorScheme.surface.copy(alpha = 0.5f),
colorScheme.surface.copy(alpha = 1.0f),
)
)
}
}

View file

@ -0,0 +1,35 @@
package com.pixelized.rplexicon.ui.composable.remember
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
/**
* https://stackoverflow.com/questions/73333935/current-scroll-position-value-in-pixels-in-lazycolumn-jetpack-compose
*/
@Composable
fun rememberCurrentOffset(state: LazyListState): State<Int> {
val position = remember { derivedStateOf { state.firstVisibleItemIndex } }
val itemOffset = remember { derivedStateOf { state.firstVisibleItemScrollOffset } }
val lastPosition = rememberPrevious(position.value)
val lastItemOffset = rememberPrevious(itemOffset.value)
val currentOffset = remember { mutableStateOf(0) }
LaunchedEffect(position.value, itemOffset.value) {
if (lastPosition == null || position.value == 0) {
currentOffset.value = itemOffset.value
} else if (lastPosition == position.value) {
currentOffset.value += (itemOffset.value - (lastItemOffset ?: 0))
} else if (lastPosition > position.value) {
currentOffset.value -= (lastItemOffset ?: 0)
} else { // lastPosition.value < position.value
currentOffset.value += itemOffset.value
}
}
return currentOffset
}

View file

@ -0,0 +1,21 @@
package com.pixelized.rplexicon.ui.composable.remember
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
@Composable
fun <T> rememberPrevious(
current: T,
shouldUpdate: (prev: T?, curr: T) -> Boolean = { a: T?, b: T -> a != b },
): T? {
val ref = rememberRef<T>()
// launched after render, so the current render will have the old value anyway
SideEffect {
if (shouldUpdate(ref.value, current)) {
ref.value = current
}
}
return ref.value
}

View file

@ -0,0 +1,23 @@
package com.pixelized.rplexicon.ui.composable.remember
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.remember
/**
* Returns a dummy MutableState that does not cause render when setting it
*/
@Composable
fun <T> rememberRef(): MutableState<T?> {
// for some reason it always recreated the value with vararg keys,
// leaving out the keys as a parameter for remember for now
return remember {
object : MutableState<T?> {
override var value: T? = null
override fun component1(): T? = value
override fun component2(): (T?) -> Unit = { value = it }
}
}
}

View file

@ -4,7 +4,6 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.net.Uri import android.net.Uri
import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -35,14 +34,10 @@ import androidx.compose.runtime.Stable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed import androidx.compose.ui.composed
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
@ -60,6 +55,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.R import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.model.Lexicon import com.pixelized.rplexicon.model.Lexicon
import com.pixelized.rplexicon.ui.composable.AsyncImage import com.pixelized.rplexicon.ui.composable.AsyncImage
import com.pixelized.rplexicon.ui.composable.BackgroundImage
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.theme.LexiconTheme import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.composable.stringResource import com.pixelized.rplexicon.utilitary.composable.stringResource
@ -197,32 +193,13 @@ private fun LexiconDetailContent(
modifier = Modifier.padding(paddingValues = paddingValues), modifier = Modifier.padding(paddingValues = paddingValues),
) { ) {
annotatedItem.portrait.firstOrNull()?.let { uri -> annotatedItem.portrait.firstOrNull()?.let { uri ->
Box( BackgroundImage(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(ratio = 1f) .aspectRatio(ratio = 1f)
.scrollOffset(scrollState = state) { -it / 2 }, .scrollOffset(scrollState = state) { -it / 2 },
) { model = { uri.toString() },
AsyncImage( )
modifier = Modifier.matchParentSize(),
imageOptions = ImageOptions(
alignment = Alignment.TopCenter,
contentScale = ContentScale.Crop,
colorFilter = remember {
ColorFilter.colorMatrix(
ColorMatrix().also { it.setToSaturation(0f) }
)
},
),
imageModel = { uri.toString() },
previewPlaceholder = R.drawable.im_brulkhai,
)
Box(
modifier = Modifier
.matchParentSize()
.background(brush = rememberBackgroundGradient())
)
}
} }
Column( Column(
modifier = Modifier modifier = Modifier
@ -342,19 +319,6 @@ private fun LexiconDetailContent(
} }
} }
@Composable
private fun rememberBackgroundGradient(): Brush {
val colorScheme = MaterialTheme.colorScheme
return remember {
Brush.verticalGradient(
colors = listOf(
colorScheme.surface.copy(alpha = 0.5f),
colorScheme.surface.copy(alpha = 1.0f),
)
)
}
}
@Composable @Composable
private fun rememberPortraitWidth(): Dp { private fun rememberPortraitWidth(): Dp {
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current

View file

@ -1,17 +1,22 @@
package com.pixelized.rplexicon.ui.screens.quest.detail package com.pixelized.rplexicon.ui.screens.quest.detail
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -27,10 +32,12 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
@ -39,9 +46,12 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.R import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.composable.BackgroundImage
import com.pixelized.rplexicon.ui.composable.remember.rememberCurrentOffset
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.theme.LexiconTheme import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.lexicon import com.pixelized.rplexicon.utilitary.extentions.lexicon
@ -50,6 +60,7 @@ import java.lang.Integer.min
@Stable @Stable
data class QuestDetailUio( data class QuestDetailUio(
val id: Int, val id: Int,
val background: Uri?,
val title: String, val title: String,
val steps: List<QuestStep>, val steps: List<QuestStep>,
) { ) {
@ -68,6 +79,7 @@ data class QuestDetailUio(
@Stable @Stable
data class AnnotatedQuestDetailUio( data class AnnotatedQuestDetailUio(
val title: String, val title: String,
val background: Uri?,
val steps: List<AnnotatedQuestStep>, val steps: List<AnnotatedQuestStep>,
) { ) {
@Stable @Stable
@ -89,6 +101,7 @@ private fun QuestDetailUio.annotate(): AnnotatedQuestDetailUio {
return remember { return remember {
AnnotatedQuestDetailUio( AnnotatedQuestDetailUio(
title = title, title = title,
background = background,
steps = annotatedSteps, steps = annotatedSteps,
) )
} }
@ -143,6 +156,7 @@ private fun QuestDetailContent(
item: State<QuestDetailUio>, item: State<QuestDetailUio>,
onBack: () -> Unit, onBack: () -> Unit,
) { ) {
val state = rememberLazyListState()
val annotatedQuest = item.value.annotate() val annotatedQuest = item.value.annotate()
Scaffold( Scaffold(
@ -164,128 +178,158 @@ private fun QuestDetailContent(
) )
}, },
content = { padding -> content = { padding ->
LazyColumn( Surface(
modifier = Modifier.padding(padding), modifier = Modifier.padding(padding),
contentPadding = PaddingValues(
top = 40.dp,
bottom = 16.dp,
start = 16.dp,
end = 16.dp
),
verticalArrangement = Arrangement.spacedBy(40.dp),
) { ) {
item { BackgroundImage(
Column { modifier = Modifier
Text( .fillMaxWidth()
modifier = Modifier.fillMaxWidth(), .aspectRatio(ratio = 1f)
textAlign = TextAlign.Center, .scrollOffset(scrollState = state) { -it / 2 },
style = MaterialTheme.typography.displaySmall, model = { annotatedQuest.background },
text = annotatedQuest.title, )
) LazyColumn(
Image( state = state,
modifier = Modifier contentPadding = PaddingValues(
.height(24.dp) top = 248.dp,
.graphicsLayer { rotationZ = 180f } bottom = 16.dp,
.align(Alignment.CenterHorizontally), start = 16.dp,
painter = painterResource(id = R.drawable.art_divider_1), end = 16.dp
contentScale = ContentScale.FillWidth, ),
alignment = Alignment.Center, verticalArrangement = Arrangement.spacedBy(0.dp),
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface), ) {
contentDescription = null, item {
) Column {
} Text(
}
items(annotatedQuest.steps) { quest ->
Column {
quest.subtitle?.let { subtitle ->
Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically, textAlign = TextAlign.Center,
horizontalArrangement = Arrangement.Center, style = MaterialTheme.typography.displaySmall,
) { text = annotatedQuest.title,
Image( )
modifier = Modifier.graphicsLayer { rotationY = 180f }, Image(
painter = painterResource(id = R.drawable.art_clip_1), modifier = Modifier
contentScale = ContentScale.FillWidth, .height(24.dp)
alignment = Alignment.Center, .graphicsLayer { rotationZ = 180f }
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface), .align(Alignment.CenterHorizontally),
contentDescription = null, painter = painterResource(id = R.drawable.art_divider_1),
) contentScale = ContentScale.FillWidth,
Text( alignment = Alignment.Center,
modifier = Modifier.padding(all = 8.dp), colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
textAlign = TextAlign.Center, contentDescription = null,
style = MaterialTheme.typography.titleLarge, )
text = subtitle, }
) }
Image( items(annotatedQuest.steps) { quest ->
painter = painterResource(id = R.drawable.art_clip_1), Column(
contentScale = ContentScale.FillWidth, verticalArrangement = Arrangement.spacedBy(16.dp),
alignment = Alignment.Center, ) {
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface), quest.subtitle?.let { subtitle ->
contentDescription = null, Row(
) 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.padding(horizontal = 8.dp),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLarge,
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,
)
}
} }
} quest.giver?.let {
quest.giver?.let { Column {
Text( Text(
modifier = Modifier.padding(top = 8.dp), style = MaterialTheme.typography.bodyMedium,
style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold,
fontWeight = FontWeight.Bold, text = "Commanditaire",
text = "Commanditaire", )
) Text(
style = MaterialTheme.typography.bodyMedium,
text = it,
)
}
}
quest.place?.let {
Column {
Text(
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
text = "Lieu",
)
Text(
style = MaterialTheme.typography.bodyMedium,
text = it,
)
}
}
quest.globalReward?.let {
Column {
Text(
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
text = "Récompense de groupe",
)
Text(
style = MaterialTheme.typography.bodyMedium,
text = it,
)
}
}
quest.individualReward?.let {
Column {
Text(
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
text = "Récompense individuelle",
)
Text(
style = MaterialTheme.typography.bodyMedium,
text = it,
)
}
}
Text( Text(
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
text = it, text = quest.description,
) )
} }
quest.place?.let {
Text(
modifier = Modifier.padding(top = 8.dp),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
text = "Lieu",
)
Text(
style = MaterialTheme.typography.bodyMedium,
text = it,
)
}
quest.globalReward?.let {
Text(
modifier = Modifier.padding(top = 8.dp),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
text = "Récompense de groupe",
)
Text(
style = MaterialTheme.typography.bodyMedium,
text = it,
)
}
quest.individualReward?.let {
Text(
modifier = Modifier.padding(top = 8.dp),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
text = "Récompense individuelle",
)
Text(
style = MaterialTheme.typography.bodyMedium,
text = it,
)
}
Text(
modifier = Modifier.padding(top = 24.dp),
style = MaterialTheme.typography.bodyMedium,
text = quest.description,
)
} }
} }
} }
}, },
) )
} }
@Stable
private fun Modifier.scrollOffset(
scrollState: LazyListState,
block: (Dp) -> Dp
): Modifier = composed {
val scroll = rememberCurrentOffset(scrollState)
val density = LocalDensity.current
this.offset(y = with(density) { block(scroll.value.toDp()) })
}
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@ -307,6 +351,7 @@ private class QuestDetailPreviewProvider : PreviewParameterProvider<State<QuestD
mutableStateOf( mutableStateOf(
QuestDetailUio( QuestDetailUio(
id = 0, id = 0,
background = Uri.parse("https://as1.ftcdn.net/v2/jpg/05/50/22/58/1000_F_550225869_jAkLTRVb7ym7EHJYvDApVXQnpANvRd8O.jpg"),
title = "La chasse aux loups", title = "La chasse aux loups",
steps = listOf( steps = listOf(
QuestDetailUio.QuestStep( QuestDetailUio.QuestStep(
@ -333,6 +378,7 @@ private class QuestDetailPreviewProvider : PreviewParameterProvider<State<QuestD
mutableStateOf( mutableStateOf(
QuestDetailUio( QuestDetailUio(
id = 1, id = 1,
background = Uri.parse("https://cdnb.artstation.com/p/assets/images/images/008/823/761/large/jon-pintar-adventurers-caravan-jon-pintar.jpg?1515529013"),
title = "Les enfants de la caravanes", title = "Les enfants de la caravanes",
steps = listOf( steps = listOf(
QuestDetailUio.QuestStep( QuestDetailUio.QuestStep(
@ -348,5 +394,4 @@ private class QuestDetailPreviewProvider : PreviewParameterProvider<State<QuestD
) )
), ),
) )
} }

View file

@ -23,6 +23,7 @@ class QuestDetailViewModel @Inject constructor(
quest = mutableStateOf( quest = mutableStateOf(
QuestDetailUio( QuestDetailUio(
id = source.id, id = source.id,
background = source.background,
title = source.title, title = source.title,
steps = source.entries.map { entry -> steps = source.entries.map { entry ->
QuestDetailUio.QuestStep( QuestDetailUio.QuestStep(

View file

@ -79,7 +79,7 @@ private fun QuestListContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
lazyColumnState: LazyListState, lazyColumnState: LazyListState,
paddingValues: PaddingValues = PaddingValues( paddingValues: PaddingValues = PaddingValues(
top = 6.dp, top = 8.dp,
bottom = 8.dp + 16.dp + 56.dp + 16.dp, bottom = 8.dp + 16.dp + 56.dp + 16.dp,
), ),
refreshState: PullRefreshState, refreshState: PullRefreshState,

View file

@ -1,5 +1,8 @@
package com.pixelized.rplexicon.utilitary.extentions package com.pixelized.rplexicon.utilitary.extentions
import android.net.Uri
import androidx.core.net.toUri
val String.ARG: String get() = "$this={$this}" val String.ARG: String get() = "$this={$this}"
val String?.highlightRegex: Regex? val String?.highlightRegex: Regex?
@ -15,3 +18,9 @@ val String?.finderRegex: Regex?
) )
} }
fun String?.toUriOrNull(): Uri? = try {
this?.takeIf { it.isNotBlank() }?.toUri()
} catch (_: Exception) {
null
}