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
 | 
			
		||||
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import androidx.compose.runtime.Stable
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
data class Quest(
 | 
			
		||||
    val id: Int,
 | 
			
		||||
    val title: String,
 | 
			
		||||
    val background: Uri?,
 | 
			
		||||
    val entries: List<QuestEntry>,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -20,4 +22,5 @@ data class QuestEntry(
 | 
			
		|||
    val groupReward: String?,
 | 
			
		||||
    val individualReward: 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.extentions.checkSheetStructure
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.toUriOrNull
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
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>?.diminutive: Int get() = this?.getValue(Sheet.DIMINUTIVE) ?: 1
 | 
			
		||||
    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.extentions.checkSheetStructure
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.toUriOrNull
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
| 
						 | 
				
			
			@ -82,6 +83,7 @@ class QuestRepository @Inject constructor(
 | 
			
		|||
            Quest(
 | 
			
		||||
                id = index,
 | 
			
		||||
                title = item,
 | 
			
		||||
                background = questMap[item]?.mapNotNull { it.background }?.randomOrNull(),
 | 
			
		||||
                entries = questMap[item] ?: emptyList(),
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -96,24 +98,26 @@ class QuestRepository @Inject constructor(
 | 
			
		|||
    ): QuestEntry? {
 | 
			
		||||
        val title = row?.getOrNull(sheetStructure.title) 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 area = row?.getOrNull(sheetStructure.area) as? String?
 | 
			
		||||
        val groupReward = row?.getOrNull(sheetStructure.groupReward) as? String?
 | 
			
		||||
        val individualReward = row?.getOrNull(sheetStructure.individualReward) 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) {
 | 
			
		||||
            QuestEntry(
 | 
			
		||||
                sheetIndex = sheetIndex,
 | 
			
		||||
                title = title,
 | 
			
		||||
                subtitle = subtitle?.takeIf { it.isNotBlank() },
 | 
			
		||||
                complete = complete,
 | 
			
		||||
                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() },
 | 
			
		||||
                description = description,
 | 
			
		||||
                background = background?.toUriOrNull(),
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            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>?.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>?.background: Int get() = this?.getValue(Sheet.BACKGROUND) ?: 8
 | 
			
		||||
 | 
			
		||||
    private object Sheet {
 | 
			
		||||
        const val ID = "1sDAay8DjbRYKM39MvEXWs-RuvyxjOFpOfRZLAEWjIUY"
 | 
			
		||||
| 
						 | 
				
			
			@ -143,6 +148,7 @@ class QuestRepository @Inject constructor(
 | 
			
		|||
            "Récompense de groupe",
 | 
			
		||||
            "Récompense individuelle",
 | 
			
		||||
            "Description",
 | 
			
		||||
            "fond"
 | 
			
		||||
        )
 | 
			
		||||
        val TITLE = COLUMNS[0]
 | 
			
		||||
        val SUBTITLE = COLUMNS[1]
 | 
			
		||||
| 
						 | 
				
			
			@ -152,5 +158,6 @@ class QuestRepository @Inject constructor(
 | 
			
		|||
        val G_REWARD = COLUMNS[5]
 | 
			
		||||
        val I_REWARD = COLUMNS[6]
 | 
			
		||||
        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.net.Uri
 | 
			
		||||
import androidx.compose.foundation.ScrollState
 | 
			
		||||
import androidx.compose.foundation.background
 | 
			
		||||
import androidx.compose.foundation.layout.Arrangement
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
| 
						 | 
				
			
			@ -35,14 +34,10 @@ 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.composed
 | 
			
		||||
import androidx.compose.ui.geometry.Offset
 | 
			
		||||
import androidx.compose.ui.graphics.Brush
 | 
			
		||||
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.layout.ContentScale
 | 
			
		||||
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.model.Lexicon
 | 
			
		||||
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.theme.LexiconTheme
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.composable.stringResource
 | 
			
		||||
| 
						 | 
				
			
			@ -197,32 +193,13 @@ private fun LexiconDetailContent(
 | 
			
		|||
            modifier = Modifier.padding(paddingValues = paddingValues),
 | 
			
		||||
        ) {
 | 
			
		||||
            annotatedItem.portrait.firstOrNull()?.let { uri ->
 | 
			
		||||
                Box(
 | 
			
		||||
                BackgroundImage(
 | 
			
		||||
                    modifier = Modifier
 | 
			
		||||
                        .fillMaxWidth()
 | 
			
		||||
                        .aspectRatio(ratio = 1f)
 | 
			
		||||
                        .scrollOffset(scrollState = state) { -it / 2 },
 | 
			
		||||
                ) {
 | 
			
		||||
                    AsyncImage(
 | 
			
		||||
                        modifier = Modifier.matchParentSize(),
 | 
			
		||||
                        imageOptions = ImageOptions(
 | 
			
		||||
                            alignment = Alignment.TopCenter,
 | 
			
		||||
                            contentScale = ContentScale.Crop,
 | 
			
		||||
                            colorFilter = remember {
 | 
			
		||||
                                ColorFilter.colorMatrix(
 | 
			
		||||
                                    ColorMatrix().also { it.setToSaturation(0f) }
 | 
			
		||||
                    model = { uri.toString() },
 | 
			
		||||
                )
 | 
			
		||||
                            },
 | 
			
		||||
                        ),
 | 
			
		||||
                        imageModel = { uri.toString() },
 | 
			
		||||
                        previewPlaceholder = R.drawable.im_brulkhai,
 | 
			
		||||
                    )
 | 
			
		||||
                    Box(
 | 
			
		||||
                        modifier = Modifier
 | 
			
		||||
                            .matchParentSize()
 | 
			
		||||
                            .background(brush = rememberBackgroundGradient())
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Column(
 | 
			
		||||
                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
 | 
			
		||||
private fun rememberPortraitWidth(): Dp {
 | 
			
		||||
    val configuration = LocalConfiguration.current
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,17 +1,22 @@
 | 
			
		|||
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.layout.Arrangement
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.PaddingValues
 | 
			
		||||
import androidx.compose.foundation.layout.Row
 | 
			
		||||
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.offset
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.lazy.LazyColumn
 | 
			
		||||
import androidx.compose.foundation.lazy.LazyListState
 | 
			
		||||
import androidx.compose.foundation.lazy.items
 | 
			
		||||
import androidx.compose.foundation.lazy.rememberLazyListState
 | 
			
		||||
import androidx.compose.material3.ExperimentalMaterial3Api
 | 
			
		||||
import androidx.compose.material3.Icon
 | 
			
		||||
import androidx.compose.material3.IconButton
 | 
			
		||||
| 
						 | 
				
			
			@ -27,10 +32,12 @@ import androidx.compose.runtime.mutableStateOf
 | 
			
		|||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.composed
 | 
			
		||||
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.platform.LocalDensity
 | 
			
		||||
import androidx.compose.ui.res.painterResource
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
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.PreviewParameter
 | 
			
		||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
 | 
			
		||||
import androidx.compose.ui.unit.Dp
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import androidx.hilt.navigation.compose.hiltViewModel
 | 
			
		||||
import com.pixelized.rplexicon.R
 | 
			
		||||
import com.pixelized.rplexicon.ui.composable.BackgroundImage
 | 
			
		||||
import com.pixelized.rplexicon.ui.composable.remember.rememberCurrentOffset
 | 
			
		||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
 | 
			
		||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
 | 
			
		||||
| 
						 | 
				
			
			@ -50,6 +60,7 @@ import java.lang.Integer.min
 | 
			
		|||
@Stable
 | 
			
		||||
data class QuestDetailUio(
 | 
			
		||||
    val id: Int,
 | 
			
		||||
    val background: Uri?,
 | 
			
		||||
    val title: String,
 | 
			
		||||
    val steps: List<QuestStep>,
 | 
			
		||||
) {
 | 
			
		||||
| 
						 | 
				
			
			@ -68,6 +79,7 @@ data class QuestDetailUio(
 | 
			
		|||
@Stable
 | 
			
		||||
data class AnnotatedQuestDetailUio(
 | 
			
		||||
    val title: String,
 | 
			
		||||
    val background: Uri?,
 | 
			
		||||
    val steps: List<AnnotatedQuestStep>,
 | 
			
		||||
) {
 | 
			
		||||
    @Stable
 | 
			
		||||
| 
						 | 
				
			
			@ -89,6 +101,7 @@ private fun QuestDetailUio.annotate(): AnnotatedQuestDetailUio {
 | 
			
		|||
    return remember {
 | 
			
		||||
        AnnotatedQuestDetailUio(
 | 
			
		||||
            title = title,
 | 
			
		||||
            background = background,
 | 
			
		||||
            steps = annotatedSteps,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -143,6 +156,7 @@ private fun QuestDetailContent(
 | 
			
		|||
    item: State<QuestDetailUio>,
 | 
			
		||||
    onBack: () -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    val state = rememberLazyListState()
 | 
			
		||||
    val annotatedQuest = item.value.annotate()
 | 
			
		||||
 | 
			
		||||
    Scaffold(
 | 
			
		||||
| 
						 | 
				
			
			@ -164,15 +178,25 @@ private fun QuestDetailContent(
 | 
			
		|||
            )
 | 
			
		||||
        },
 | 
			
		||||
        content = { padding ->
 | 
			
		||||
            LazyColumn(
 | 
			
		||||
            Surface(
 | 
			
		||||
                modifier = Modifier.padding(padding),
 | 
			
		||||
            ) {
 | 
			
		||||
                BackgroundImage(
 | 
			
		||||
                    modifier = Modifier
 | 
			
		||||
                        .fillMaxWidth()
 | 
			
		||||
                        .aspectRatio(ratio = 1f)
 | 
			
		||||
                        .scrollOffset(scrollState = state) { -it / 2 },
 | 
			
		||||
                    model = { annotatedQuest.background },
 | 
			
		||||
                )
 | 
			
		||||
                LazyColumn(
 | 
			
		||||
                    state = state,
 | 
			
		||||
                    contentPadding = PaddingValues(
 | 
			
		||||
                    top = 40.dp,
 | 
			
		||||
                        top = 248.dp,
 | 
			
		||||
                        bottom = 16.dp,
 | 
			
		||||
                        start = 16.dp,
 | 
			
		||||
                        end = 16.dp
 | 
			
		||||
                    ),
 | 
			
		||||
                verticalArrangement = Arrangement.spacedBy(40.dp),
 | 
			
		||||
                    verticalArrangement = Arrangement.spacedBy(0.dp),
 | 
			
		||||
                ) {
 | 
			
		||||
                    item {
 | 
			
		||||
                        Column {
 | 
			
		||||
| 
						 | 
				
			
			@ -196,10 +220,14 @@ private fun QuestDetailContent(
 | 
			
		|||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    items(annotatedQuest.steps) { quest ->
 | 
			
		||||
                    Column {
 | 
			
		||||
                        Column(
 | 
			
		||||
                            verticalArrangement = Arrangement.spacedBy(16.dp),
 | 
			
		||||
                        ) {
 | 
			
		||||
                            quest.subtitle?.let { subtitle ->
 | 
			
		||||
                                Row(
 | 
			
		||||
                                modifier = Modifier.fillMaxWidth(),
 | 
			
		||||
                                    modifier = Modifier
 | 
			
		||||
                                        .fillMaxWidth()
 | 
			
		||||
                                        .padding(top = 64.dp),
 | 
			
		||||
                                    verticalAlignment = Alignment.CenterVertically,
 | 
			
		||||
                                    horizontalArrangement = Arrangement.Center,
 | 
			
		||||
                                ) {
 | 
			
		||||
| 
						 | 
				
			
			@ -212,7 +240,7 @@ private fun QuestDetailContent(
 | 
			
		|||
                                        contentDescription = null,
 | 
			
		||||
                                    )
 | 
			
		||||
                                    Text(
 | 
			
		||||
                                    modifier = Modifier.padding(all = 8.dp),
 | 
			
		||||
                                        modifier = Modifier.padding(horizontal = 8.dp),
 | 
			
		||||
                                        textAlign = TextAlign.Center,
 | 
			
		||||
                                        style = MaterialTheme.typography.titleLarge,
 | 
			
		||||
                                        text = subtitle,
 | 
			
		||||
| 
						 | 
				
			
			@ -227,8 +255,8 @@ private fun QuestDetailContent(
 | 
			
		|||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            quest.giver?.let {
 | 
			
		||||
                                Column {
 | 
			
		||||
                                    Text(
 | 
			
		||||
                                modifier = Modifier.padding(top = 8.dp),
 | 
			
		||||
                                        style = MaterialTheme.typography.bodyMedium,
 | 
			
		||||
                                        fontWeight = FontWeight.Bold,
 | 
			
		||||
                                        text = "Commanditaire",
 | 
			
		||||
| 
						 | 
				
			
			@ -238,9 +266,10 @@ private fun QuestDetailContent(
 | 
			
		|||
                                        text = it,
 | 
			
		||||
                                    )
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            quest.place?.let {
 | 
			
		||||
                                Column {
 | 
			
		||||
                                    Text(
 | 
			
		||||
                                modifier = Modifier.padding(top = 8.dp),
 | 
			
		||||
                                        style = MaterialTheme.typography.bodyMedium,
 | 
			
		||||
                                        fontWeight = FontWeight.Bold,
 | 
			
		||||
                                        text = "Lieu",
 | 
			
		||||
| 
						 | 
				
			
			@ -250,9 +279,10 @@ private fun QuestDetailContent(
 | 
			
		|||
                                        text = it,
 | 
			
		||||
                                    )
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            quest.globalReward?.let {
 | 
			
		||||
                                Column {
 | 
			
		||||
                                    Text(
 | 
			
		||||
                                modifier = Modifier.padding(top = 8.dp),
 | 
			
		||||
                                        style = MaterialTheme.typography.bodyMedium,
 | 
			
		||||
                                        fontWeight = FontWeight.Bold,
 | 
			
		||||
                                        text = "Récompense de groupe",
 | 
			
		||||
| 
						 | 
				
			
			@ -262,9 +292,10 @@ private fun QuestDetailContent(
 | 
			
		|||
                                        text = it,
 | 
			
		||||
                                    )
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            quest.individualReward?.let {
 | 
			
		||||
                                Column {
 | 
			
		||||
                                    Text(
 | 
			
		||||
                                modifier = Modifier.padding(top = 8.dp),
 | 
			
		||||
                                        style = MaterialTheme.typography.bodyMedium,
 | 
			
		||||
                                        fontWeight = FontWeight.Bold,
 | 
			
		||||
                                        text = "Récompense individuelle",
 | 
			
		||||
| 
						 | 
				
			
			@ -274,18 +305,31 @@ private fun QuestDetailContent(
 | 
			
		|||
                                        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
 | 
			
		||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
 | 
			
		||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
 | 
			
		||||
| 
						 | 
				
			
			@ -307,6 +351,7 @@ private class QuestDetailPreviewProvider : PreviewParameterProvider<State<QuestD
 | 
			
		|||
        mutableStateOf(
 | 
			
		||||
            QuestDetailUio(
 | 
			
		||||
                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",
 | 
			
		||||
                steps = listOf(
 | 
			
		||||
                    QuestDetailUio.QuestStep(
 | 
			
		||||
| 
						 | 
				
			
			@ -333,6 +378,7 @@ private class QuestDetailPreviewProvider : PreviewParameterProvider<State<QuestD
 | 
			
		|||
        mutableStateOf(
 | 
			
		||||
            QuestDetailUio(
 | 
			
		||||
                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",
 | 
			
		||||
                steps = listOf(
 | 
			
		||||
                    QuestDetailUio.QuestStep(
 | 
			
		||||
| 
						 | 
				
			
			@ -349,4 +395,3 @@ private class QuestDetailPreviewProvider : PreviewParameterProvider<State<QuestD
 | 
			
		|||
        ),
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,7 @@ class QuestDetailViewModel @Inject constructor(
 | 
			
		|||
        quest = mutableStateOf(
 | 
			
		||||
            QuestDetailUio(
 | 
			
		||||
                id = source.id,
 | 
			
		||||
                background = source.background,
 | 
			
		||||
                title = source.title,
 | 
			
		||||
                steps = source.entries.map { entry ->
 | 
			
		||||
                    QuestDetailUio.QuestStep(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -79,7 +79,7 @@ private fun QuestListContent(
 | 
			
		|||
    modifier: Modifier = Modifier,
 | 
			
		||||
    lazyColumnState: LazyListState,
 | 
			
		||||
    paddingValues: PaddingValues = PaddingValues(
 | 
			
		||||
        top = 6.dp,
 | 
			
		||||
        top = 8.dp,
 | 
			
		||||
        bottom = 8.dp + 16.dp + 56.dp + 16.dp,
 | 
			
		||||
    ),
 | 
			
		||||
    refreshState: PullRefreshState,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,8 @@
 | 
			
		|||
package com.pixelized.rplexicon.utilitary.extentions
 | 
			
		||||
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import androidx.core.net.toUri
 | 
			
		||||
 | 
			
		||||
val String.ARG: String get() = "$this={$this}"
 | 
			
		||||
 | 
			
		||||
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