diff --git a/shared/src/commonMain/kotlin/App.kt b/shared/src/commonMain/kotlin/App.kt index 6a47d0f..7074dd9 100644 --- a/shared/src/commonMain/kotlin/App.kt +++ b/shared/src/commonMain/kotlin/App.kt @@ -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, diff --git a/shared/src/commonMain/kotlin/details/RecipeDetails.kt b/shared/src/commonMain/kotlin/details/RecipeDetails.kt index 9b90732..89b8127 100644 --- a/shared/src/commonMain/kotlin/details/RecipeDetails.kt +++ b/shared/src/commonMain/kotlin/details/RecipeDetails.kt @@ -24,7 +24,7 @@ fun RecipeDetails( if (isLarge) RecipeDetailsLarge( recipe = recipe, goBack = goBack, - sensorManager = sensorManager + sensorManager = sensorManager, ) else RecipeDetailsSmall( recipe = recipe, diff --git a/shared/src/commonMain/kotlin/details/RecipeDetailsLarge.kt b/shared/src/commonMain/kotlin/details/RecipeDetailsLarge.kt index 5a9b542..5e04732 100644 --- a/shared/src/commonMain/kotlin/details/RecipeDetailsLarge.kt +++ b/shared/src/commonMain/kotlin/details/RecipeDetailsLarge.kt @@ -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) { ) } } -} \ No newline at end of file +} + + diff --git a/shared/src/commonMain/kotlin/details/StepsAndDetails.kt b/shared/src/commonMain/kotlin/details/StepsAndDetails.kt index 68ad097..a0d9fd6 100644 --- a/shared/src/commonMain/kotlin/details/StepsAndDetails.kt +++ b/shared/src/commonMain/kotlin/details/StepsAndDetails.kt @@ -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) })