Add background image to quest.
This commit is contained in:
parent
331fbb87e5
commit
6a326ad1a8
12 changed files with 321 additions and 158 deletions
|
|
@ -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?,
|
||||||
)
|
)
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,15 +178,25 @@ private fun QuestDetailContent(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
content = { padding ->
|
content = { padding ->
|
||||||
LazyColumn(
|
Surface(
|
||||||
modifier = Modifier.padding(padding),
|
modifier = Modifier.padding(padding),
|
||||||
|
) {
|
||||||
|
BackgroundImage(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(ratio = 1f)
|
||||||
|
.scrollOffset(scrollState = state) { -it / 2 },
|
||||||
|
model = { annotatedQuest.background },
|
||||||
|
)
|
||||||
|
LazyColumn(
|
||||||
|
state = state,
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(
|
||||||
top = 40.dp,
|
top = 248.dp,
|
||||||
bottom = 16.dp,
|
bottom = 16.dp,
|
||||||
start = 16.dp,
|
start = 16.dp,
|
||||||
end = 16.dp
|
end = 16.dp
|
||||||
),
|
),
|
||||||
verticalArrangement = Arrangement.spacedBy(40.dp),
|
verticalArrangement = Arrangement.spacedBy(0.dp),
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
Column {
|
Column {
|
||||||
|
|
@ -196,10 +220,14 @@ private fun QuestDetailContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items(annotatedQuest.steps) { quest ->
|
items(annotatedQuest.steps) { quest ->
|
||||||
Column {
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
) {
|
||||||
quest.subtitle?.let { subtitle ->
|
quest.subtitle?.let { subtitle ->
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 64.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
) {
|
) {
|
||||||
|
|
@ -212,7 +240,7 @@ private fun QuestDetailContent(
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(all = 8.dp),
|
modifier = Modifier.padding(horizontal = 8.dp),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
text = subtitle,
|
text = subtitle,
|
||||||
|
|
@ -227,8 +255,8 @@ private fun QuestDetailContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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",
|
||||||
|
|
@ -238,9 +266,10 @@ private fun QuestDetailContent(
|
||||||
text = it,
|
text = it,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
quest.place?.let {
|
quest.place?.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 = "Lieu",
|
text = "Lieu",
|
||||||
|
|
@ -250,9 +279,10 @@ private fun QuestDetailContent(
|
||||||
text = it,
|
text = it,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
quest.globalReward?.let {
|
quest.globalReward?.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 = "Récompense de groupe",
|
text = "Récompense de groupe",
|
||||||
|
|
@ -262,9 +292,10 @@ private fun QuestDetailContent(
|
||||||
text = it,
|
text = it,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
quest.individualReward?.let {
|
quest.individualReward?.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 = "Récompense individuelle",
|
text = "Récompense individuelle",
|
||||||
|
|
@ -274,18 +305,31 @@ private fun QuestDetailContent(
|
||||||
text = it,
|
text = it,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(top = 24.dp),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
text = quest.description,
|
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(
|
||||||
|
|
@ -349,4 +395,3 @@ private class QuestDetailPreviewProvider : PreviewParameterProvider<State<QuestD
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue