Three Column display for large view #4

Merged
Azea_Avenbright merged 1 commit from StepsAndDetailsLarge into master 2025-02-28 18:54:43 -05:00
4 changed files with 170 additions and 167 deletions

View file

@ -134,10 +134,11 @@ fun App(sensorManager: SensorManager?, isLarge: Boolean = false) {
}
val listState = rememberLazyGridState()
LazyVerticalGrid(
state = listState, columns = GridCells.Fixed(if(isLarge) 10 else 4))
state = listState, columns = GridCells.Fixed(if (isLarge) 10 else 4)
)
{
items(tags.size) {
Button(onClick = {tags.remove(tags[it])}) {
Button(onClick = { tags.remove(tags[it]) }) {
Text(text = tags[it])
}
}
@ -177,7 +178,7 @@ fun App(sensorManager: SensorManager?, isLarge: Boolean = false) {
}
composable(
route = RecipeAppScreen.List.name
) {
) {
searchBar = true
RecipesListScreen(
isLarge = isLarge,

View file

@ -24,7 +24,7 @@ fun RecipeDetails(
if (isLarge) RecipeDetailsLarge(
recipe = recipe,
goBack = goBack,
sensorManager = sensorManager
sensorManager = sensorManager,
)
else RecipeDetailsSmall(
recipe = recipe,

View file

@ -1,64 +1,61 @@
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.animateIntOffsetAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
import details.IngredientItem
import details.InstructionItem
import details.StepsAndDetails
import details.ingredients
import details.ingredientsHeader
import details.recipeHeader
import details.steps
import details.stepsHeader
import model.Recipe
import org.jetbrains.compose.resources.painterResource
import sensor.Listener
import sensor.SensorData
import sensor.SensorManager
import kotlin.math.PI
@OptIn(ExperimentalSharedTransitionApi::class)
@ -67,8 +64,8 @@ fun RecipeDetailsLarge(
recipe: Recipe,
goBack: () -> Unit,
sensorManager: SensorManager?,
sideBySide: Boolean = true,
) {
//val imageRotation = remember { mutableStateOf(0) }
val sensorDataLive = remember { mutableStateOf(SensorData(0.0f, 0.0f)) }
sensorManager?.registerListener(object : Listener {
@ -82,7 +79,6 @@ fun RecipeDetailsLarge(
override fun onPreScroll(
available: Offset, source: NestedScrollSource
): Offset {
//imageRotation.value += (available.y * 0.5).toInt()
return Offset.Zero
}
@ -90,12 +86,10 @@ fun RecipeDetailsLarge(
consumed: Offset, available: Offset, source: NestedScrollSource
): Offset {
val delta = available.y
//imageRotation.value += ((delta * PI / 180) * 10).toInt()
return super.onPostScroll(consumed, available, source)
}
override suspend fun onPreFling(available: Velocity): Velocity {
//imageRotation.value += available.y.toInt()
return super.onPreFling(available)
}
}
@ -105,89 +99,59 @@ fun RecipeDetailsLarge(
modifier = Modifier.fillMaxSize()
.background(if (recipe.bgColor == MaterialTheme.colorScheme.primaryContainer) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.background)
) {
val size = mutableStateOf(IntSize(0, 0))
Row {
Box(modifier = Modifier.fillMaxSize().onGloballyPositioned {
size.value = it.size
}.weight(1f).pointerInput(Unit) {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
// on every relayout Compose will send synthetic Move event,
// so we skip it to avoid event spam
if (event.type == PointerEventType.Move) {
val position = event.changes.first().position
sensorDataLive.value = SensorData(
roll = position.x - size.value.height / 4,
pitch = (position.y - size.value.width / 4)
)
}
}
}
}) {
Card(
modifier = Modifier
.clip(RoundedCornerShape(topEnd = 35.dp, bottomEnd = 35.dp)),
shape = RoundedCornerShape(
topEnd = 35.dp,
bottomEnd = 35.dp,
),
) {
// background image + its shadow
Box(modifier = Modifier.fillMaxSize().background(recipe.bgColor)) {
// image shadows and image
Box(
modifier = Modifier.aspectRatio(1f).padding(32.dp)
.align(Alignment.Center)
) {
Box(modifier = Modifier.padding(32.dp)) {
Image(
painter = painterResource(recipe.image),
contentDescription = null,
modifier = Modifier.aspectRatio(1f).align(Alignment.Center)
.padding(16.dp)/*.rotate(imageRotation.value.toFloat())*/.size(400.dp).clip(CircleShape)
)
}
}
}
}
}
Box(
modifier = Modifier.fillMaxSize()
.background(color = if (recipe.bgColor == MaterialTheme.colorScheme.primaryContainer) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.background)
.pointerInput(Unit) {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
if (event.type == PointerEventType.Scroll) {
val position = event.changes.first().position
// on every relayout Compose will send synthetic Move event,
// so we skip it to avoid event spam
// imageRotation.value =
// (imageRotation.value + position.getDistance()
// .toInt() * 0.010).toInt()
}
}
}
}.weight(1f)
.weight(1f)
) {
val listState = rememberLazyListState()
val ingredientState = rememberLazyListState()
val stepsState = rememberLazyListState()
Box(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(
contentPadding = PaddingValues(64.dp),
userScrollEnabled = true,
verticalArrangement = Arrangement.Absolute.spacedBy(16.dp),
modifier = Modifier.fillMaxSize().nestedScroll(nestedScrollConnection),
state = listState
Column(
modifier = Modifier.padding(top = 64.dp)
) {
StepsAndDetails(
recipe = recipe
)
recipeHeader(recipe)
Row {
Column(modifier = Modifier.weight(.5f)) {
Box(modifier = Modifier.padding(32.dp)) {
Image(
painter = painterResource(recipe.image),
contentDescription = null,
modifier = Modifier.aspectRatio(1f).align(Alignment.Center)
.padding(16.dp)
.size(400.dp).clip(CircleShape)
)
}
}
Column(modifier = Modifier.weight(.5f)) {
ingredientsHeader(recipe)
LazyColumn(
contentPadding = PaddingValues(64.dp),
userScrollEnabled = true,
verticalArrangement = Arrangement.Absolute.spacedBy(16.dp),
modifier = Modifier.nestedScroll(nestedScrollConnection),
state = ingredientState
) {
ingredients(recipe)
}
}
Column(modifier = Modifier.weight(1f)) {
stepsHeader(recipe)
LazyColumn(
contentPadding = PaddingValues(64.dp),
userScrollEnabled = true,
verticalArrangement = Arrangement.Absolute.spacedBy(16.dp),
modifier = Modifier.nestedScroll(nestedScrollConnection),
state = stepsState
) {
steps(recipe)
}
}
}
}
}
}
@ -224,4 +188,6 @@ fun BackButton(goBack: () -> Unit) {
)
}
}
}
}

View file

@ -5,9 +5,11 @@ import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@ -20,90 +22,124 @@ import model.Recipe
@OptIn(ExperimentalSharedTransitionApi::class)
internal fun LazyListScope.StepsAndDetails(
recipe: Recipe
recipe: Recipe,
) {
item {
Text(
text = recipe.title,
style = MaterialTheme.typography.headlineLarge,
fontWeight = FontWeight.W700,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
Text(
text = recipe.description,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
Row {
Text(
text = "Prep Time: ".plus(recipe.prepTime.toString()),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
Text(
text = "Cook Time: ".plus(recipe.cookTime.toString()),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
}
Row {
Text(
text = "Total Time: ".plus(recipe.cookTime.plus(recipe.prepTime).toString()),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
Text(
text = "Serving Size: ".plus(recipe.servings),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
}
AnimateInEffect(
recipe = recipe,
intervalStart = 0 / (recipe.instructions.size + recipe.ingredients.size + 2).toFloat(),
content = {
Text(
text = "INGREDIENTS",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.W700,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
})
recipeHeader(recipe)
}
item {
ingredientsHeader(recipe)
}
ingredients(recipe)
item {
stepsHeader(recipe)
}
steps(recipe)
}
@Composable
fun recipeHeader(recipe: Recipe)
{
Text(
text = recipe.title,
style = MaterialTheme.typography.headlineLarge,
fontWeight = FontWeight.W700,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
Text(
text = recipe.description,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
Row {
Text(
text = "Prep Time: ".plus(recipe.prepTime.toString()),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
Text(
text = "Cook Time: ".plus(recipe.cookTime.toString()),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
}
Row {
Text(
text = "Total Time: ".plus(recipe.cookTime.plus(recipe.prepTime).toString()),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
Text(
text = "Serving Size: ".plus(recipe.servings),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
}
}
@Composable
fun ingredientsHeader(recipe: Recipe)
{
AnimateInEffect(
recipe = recipe,
intervalStart = 0 / (recipe.instructions.size + recipe.ingredients.size + 2).toFloat(),
content = {
Text(
text = "INGREDIENTS",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.W700,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
})
}
@Composable
fun stepsHeader(recipe: Recipe)
{
AnimateInEffect(
recipe = recipe,
intervalStart = (recipe.ingredients.size + 1) / (recipe.instructions.size + recipe.ingredients.size + 2).toFloat(),
content = {
Text(
text = "STEPS",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.W700,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
}
)
}
internal fun LazyListScope.ingredients(
recipe: Recipe
){
itemsIndexed(recipe.ingredients) { index, value ->
AnimateInEffect(
intervalStart = (index + 1) / (recipe.instructions.size + recipe.ingredients.size + 1).toFloat(),
intervalStart = (index + 1) / (recipe.ingredients.size + 1).toFloat(),
recipe = recipe,
content = {
IngredientItem(recipe, value)
}
)
}
}
item {
AnimateInEffect(
recipe = recipe,
intervalStart = (recipe.ingredients.size + 1) / (recipe.instructions.size + recipe.ingredients.size + 2).toFloat(),
content = {
Text(
text = "STEPS",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.W700,
modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp)
)
}
)
}
internal fun LazyListScope.steps(
recipe: Recipe
)
{
itemsIndexed(recipe.instructions) { index, _ ->
AnimateInEffect(
recipe = recipe,
intervalStart = (recipe.ingredients.size + index + 1) / (recipe.instructions.size + recipe.ingredients.size + 1).toFloat(),
intervalStart = (index + 1) / (recipe.instructions.size + 1).toFloat(),
content = {
InstructionItem(recipe, index)
})