Three Column display for large view #4
4 changed files with 170 additions and 167 deletions
|
@ -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,
|
||||
|
|
|
@ -24,7 +24,7 @@ fun RecipeDetails(
|
|||
if (isLarge) RecipeDetailsLarge(
|
||||
recipe = recipe,
|
||||
goBack = goBack,
|
||||
sensorManager = sensorManager
|
||||
sensorManager = sensorManager,
|
||||
)
|
||||
else RecipeDetailsSmall(
|
||||
recipe = recipe,
|
||||
|
|
|
@ -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) {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue