change questDetail layout to fix the background parallax

This commit is contained in:
Thomas Andres Gomez 2023-08-02 11:15:48 +02:00
parent 4967989315
commit fb0ae34b4a
6 changed files with 63 additions and 117 deletions

View file

@ -1,44 +0,0 @@
package com.pixelized.rplexicon.ui.composable.remember
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@Stable
data class LazyListOffset(
val offsetY: State<Dp>,
val connection: NestedScrollConnection,
)
@Composable
@Stable
fun rememberLazyListOffset(
lazyListState: LazyListState,
): LazyListOffset {
val density = LocalDensity.current
return remember {
val offsetY = mutableStateOf(0.dp)
LazyListOffset(
offsetY = offsetY,
connection = object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
if (lazyListState.canScrollForward || lazyListState.canScrollBackward) {
offsetY.value -= with(density) { available.y.toDp() }
}
return Offset.Zero
}
},
)
}
}

View file

@ -62,6 +62,7 @@ import com.pixelized.rplexicon.utilitary.composable.stringResource
import com.pixelized.rplexicon.utilitary.extentions.annotatedSpan import com.pixelized.rplexicon.utilitary.extentions.annotatedSpan
import com.pixelized.rplexicon.utilitary.extentions.annotatedString import com.pixelized.rplexicon.utilitary.extentions.annotatedString
import com.pixelized.rplexicon.utilitary.extentions.highlightRegex import com.pixelized.rplexicon.utilitary.extentions.highlightRegex
import com.pixelized.rplexicon.utilitary.extentions.scrollOffset
import com.skydoves.landscapist.ImageOptions import com.skydoves.landscapist.ImageOptions
@Stable @Stable
@ -189,7 +190,7 @@ private fun LexiconDetailContent(
) )
}, },
) { paddingValues -> ) { paddingValues ->
Box( Surface(
modifier = Modifier.padding(paddingValues = paddingValues), modifier = Modifier.padding(paddingValues = paddingValues),
) { ) {
annotatedItem.portrait.firstOrNull()?.let { uri -> annotatedItem.portrait.firstOrNull()?.let { uri ->
@ -332,15 +333,6 @@ private fun rememberPortraitWidth(): Dp {
} }
} }
@Stable
private fun Modifier.scrollOffset(
scrollState: ScrollState,
block: (Dp) -> Dp
): Modifier = composed {
val density = LocalDensity.current
this.offset(y = with(density) { block(scrollState.value.toDp()) })
}
@Composable @Composable
@Preview(uiMode = UI_MODE_NIGHT_NO, heightDp = 980) @Preview(uiMode = UI_MODE_NIGHT_NO, heightDp = 980)
@Preview(uiMode = UI_MODE_NIGHT_YES, heightDp = 980) @Preview(uiMode = UI_MODE_NIGHT_YES, heightDp = 980)

View file

@ -3,20 +3,18 @@ package com.pixelized.rplexicon.ui.screens.quest.detail
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
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.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio 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.rememberScrollState
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.verticalScroll
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
@ -32,11 +30,9 @@ 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.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -47,17 +43,15 @@ import androidx.compose.ui.text.style.TextOverflow
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.BackgroundImage
import com.pixelized.rplexicon.ui.composable.remember.LazyListOffset
import com.pixelized.rplexicon.ui.composable.remember.rememberLazyListOffset
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconDetail import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconDetail
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
import com.pixelized.rplexicon.utilitary.extentions.scrollOffset
import java.lang.Integer.min import java.lang.Integer.min
@Stable @Stable
@ -160,11 +154,11 @@ fun QuestDetailScreen(
@Composable @Composable
private fun QuestDetailContent( private fun QuestDetailContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
state: ScrollState = rememberScrollState(),
item: State<QuestDetailUio>, item: State<QuestDetailUio>,
onBack: () -> Unit, onBack: () -> Unit,
onGiver: (Int) -> Unit, onGiver: (Int) -> Unit,
) { ) {
val state = rememberLazyListState()
val annotatedQuest = item.value.annotate() val annotatedQuest = item.value.annotate()
Scaffold( Scaffold(
@ -186,51 +180,42 @@ private fun QuestDetailContent(
) )
}, },
content = { padding -> content = { padding ->
val lazyListOffset = rememberLazyListOffset(lazyListState = state)
Surface( Surface(
modifier = Modifier modifier = Modifier.padding(padding),
.padding(padding)
.nestedScroll(lazyListOffset.connection),
) { ) {
BackgroundImage( BackgroundImage(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(ratio = 1f) .aspectRatio(ratio = 1f)
.scrollOffset(scrollState = lazyListOffset) { -it / 2 }, .scrollOffset(scrollState = state) { -it / 2 },
model = { annotatedQuest.background }, model = { annotatedQuest.background },
) )
LazyColumn( Column(
state = state, modifier = Modifier
contentPadding = PaddingValues( .verticalScroll(state)
top = 248.dp, .padding(top = 248.dp, bottom = 16.dp, start = 16.dp, end = 16.dp),
bottom = 16.dp,
start = 16.dp,
end = 16.dp
),
verticalArrangement = Arrangement.spacedBy(0.dp),
) { ) {
item { Column {
Column { Text(
Text( modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center,
textAlign = TextAlign.Center, style = MaterialTheme.typography.displaySmall,
style = MaterialTheme.typography.displaySmall, text = annotatedQuest.title,
text = annotatedQuest.title, )
) Image(
Image( modifier = Modifier
modifier = Modifier .height(24.dp)
.height(24.dp) .graphicsLayer { rotationZ = 180f }
.graphicsLayer { rotationZ = 180f } .align(Alignment.CenterHorizontally),
.align(Alignment.CenterHorizontally), painter = painterResource(id = R.drawable.art_divider_1),
painter = painterResource(id = R.drawable.art_divider_1), contentScale = ContentScale.FillWidth,
contentScale = ContentScale.FillWidth, alignment = Alignment.Center,
alignment = Alignment.Center, colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface), contentDescription = null,
contentDescription = null, )
)
}
} }
items(annotatedQuest.steps) { quest ->
annotatedQuest.steps.forEach { quest ->
Column( Column(
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp),
) { ) {
@ -279,7 +264,7 @@ private fun QuestDetailContent(
Text( Text(
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
text = "Commanditaire", text = stringResource(id = R.string.quest_detail_giver),
) )
Text( Text(
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
@ -292,7 +277,7 @@ private fun QuestDetailContent(
Text( Text(
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
text = "Lieu", text = stringResource(id = R.string.quest_detail_area),
) )
Text( Text(
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
@ -305,7 +290,7 @@ private fun QuestDetailContent(
Text( Text(
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
text = "Récompense de groupe", text = stringResource(id = R.string.quest_detail_individual_reward),
) )
Text( Text(
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
@ -318,7 +303,7 @@ private fun QuestDetailContent(
Text( Text(
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
text = "Récompense individuelle", text = stringResource(id = R.string.quest_detail_group_rewars),
) )
Text( Text(
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
@ -335,19 +320,10 @@ private fun QuestDetailContent(
} }
} }
} }
}, },
) )
} }
@Stable
private fun Modifier.scrollOffset(
scrollState: LazyListOffset,
block: (Dp) -> Dp
): Modifier = composed {
this.offset(y = block(scrollState.offsetY.value))
}
@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)

View file

@ -3,23 +3,27 @@ package com.pixelized.rplexicon.utilitary.extentions
import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.Transition import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.spring import androidx.compose.animation.core.spring
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.google.accompanist.placeholder.PlaceholderHighlight import com.google.accompanist.placeholder.PlaceholderHighlight
import com.google.accompanist.placeholder.placeholder import com.google.accompanist.placeholder.placeholder
@Composable
fun Modifier.placeholder( fun Modifier.placeholder(
color: Color = MaterialTheme.lexicon.colorScheme.placeholder, color: Color = Color.Unspecified,
shape: Shape = CircleShape, shape: Shape = CircleShape,
highlight: PlaceholderHighlight? = null, highlight: PlaceholderHighlight? = null,
placeholderFadeTransitionSpec: @Composable Transition.Segment<Boolean>.() -> FiniteAnimationSpec<Float> = { spring() }, placeholderFadeTransitionSpec: @Composable Transition.Segment<Boolean>.() -> FiniteAnimationSpec<Float> = { spring() },
@ -28,7 +32,7 @@ fun Modifier.placeholder(
): Modifier = composed { ): Modifier = composed {
placeholder( placeholder(
visible = visible(), visible = visible(),
color = color, color = if (color == Color.Unspecified) MaterialTheme.lexicon.colorScheme.placeholder else color,
shape = shape, shape = shape,
highlight = highlight, highlight = highlight,
placeholderFadeTransitionSpec = placeholderFadeTransitionSpec, placeholderFadeTransitionSpec = placeholderFadeTransitionSpec,
@ -36,10 +40,19 @@ fun Modifier.placeholder(
) )
} }
@Composable @Stable
fun Modifier.cell() = composed { fun Modifier.cell() = composed {
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.heightIn(min = MaterialTheme.lexicon.dimens.item) .heightIn(min = MaterialTheme.lexicon.dimens.item)
.padding(horizontal = 16.dp, vertical = 4.dp) .padding(horizontal = 16.dp, vertical = 4.dp)
}
@Stable
fun Modifier.scrollOffset(
scrollState: ScrollState,
block: (Dp) -> Dp
): Modifier = composed {
val density = LocalDensity.current
this.offset(y = with(density) { block(scrollState.value.toDp()) })
} }

View file

@ -47,4 +47,8 @@
<string name="search_item_tags">Mots clés :</string> <string name="search_item_tags">Mots clés :</string>
<string name="quest_detail_title">Détails de quête</string> <string name="quest_detail_title">Détails de quête</string>
<string name="quest_detail_giver">Commanditaire :</string>
<string name="quest_detail_area">Lieu :</string>
<string name="quest_detail_individual_reward">Récompense individuelle :</string>
<string name="quest_detail_group_rewars">Récompense de groupe :</string>
</resources> </resources>

View file

@ -47,4 +47,9 @@
<string name="search_item_tags">Tags:</string> <string name="search_item_tags">Tags:</string>
<string name="quest_detail_title">Quest details</string> <string name="quest_detail_title">Quest details</string>
<string name="quest_detail_giver">Quest giver:</string>
<string name="quest_detail_area">Area:</string>
<string name="quest_detail_individual_reward">Individual reward:</string>
<string name="quest_detail_group_rewars">Group reward:</string>
</resources> </resources>