Redesign recipe detail view
This commit is contained in:
@@ -42,7 +42,6 @@
|
|||||||
<string name="ingredient_substitute_a11y">Zamień składnik</string>
|
<string name="ingredient_substitute_a11y">Zamień składnik</string>
|
||||||
|
|
||||||
<!-- Phase 2.1 — Recipe detail sheet (UI-only view; planner wiring lands in later phases) -->
|
<!-- Phase 2.1 — Recipe detail sheet (UI-only view; planner wiring lands in later phases) -->
|
||||||
<string name="recipe_detail_plan_button">Zaplanuj</string>
|
|
||||||
<string name="recipe_detail_servings_label">Porcje</string>
|
<string name="recipe_detail_servings_label">Porcje</string>
|
||||||
<string name="recipe_detail_section_ingredients">Składniki</string>
|
<string name="recipe_detail_section_ingredients">Składniki</string>
|
||||||
<string name="recipe_detail_section_steps">Kroki</string>
|
<string name="recipe_detail_section_steps">Kroki</string>
|
||||||
@@ -86,6 +85,7 @@
|
|||||||
|
|
||||||
<!-- Phase 6 — Meal plan editor (UI-first; planStore wiring lands in later phases) -->
|
<!-- Phase 6 — Meal plan editor (UI-first; planStore wiring lands in later phases) -->
|
||||||
<string name="meal_plan_editor_title">Zaplanuj posiłek</string>
|
<string name="meal_plan_editor_title">Zaplanuj posiłek</string>
|
||||||
|
<string name="meal_plan_editor_title_a11y">Dodaj posiłek do planu</string>
|
||||||
<string name="meal_plan_editor_back_a11y">Wróć do szczegółów przepisu</string>
|
<string name="meal_plan_editor_back_a11y">Wróć do szczegółów przepisu</string>
|
||||||
<string name="meal_plan_editor_confirm">Dodaj</string>
|
<string name="meal_plan_editor_confirm">Dodaj</string>
|
||||||
<string name="meal_plan_editor_confirm_a11y">Dodaj posiłek do planu</string>
|
<string name="meal_plan_editor_confirm_a11y">Dodaj posiłek do planu</string>
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package dev.ulfrx.recipe.ui.components.button
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.composeunstyled.UnstyledButton
|
||||||
|
import com.composeunstyled.UnstyledIcon
|
||||||
|
import dev.ulfrx.recipe.ui.theme.RecipeTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CircleButton(
|
||||||
|
icon: ImageVector,
|
||||||
|
contentDescription: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
size: Dp = 48.dp,
|
||||||
|
tint: Color = RecipeTheme.colors.surface,
|
||||||
|
iconTint: Color = RecipeTheme.colors.content,
|
||||||
|
iconSize: Dp = 24.dp,
|
||||||
|
borderTint: Color = RecipeTheme.colors.borderCard,
|
||||||
|
borderWidth: Dp = 1.dp,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val isPressed by interactionSource.collectIsPressedAsState()
|
||||||
|
|
||||||
|
val scale by animateFloatAsState(
|
||||||
|
targetValue = if (isPressed) 1.15f else 1f,
|
||||||
|
animationSpec = tween(durationMillis = 120, easing = FastOutSlowInEasing),
|
||||||
|
label = "CircleGlassButton scale",
|
||||||
|
)
|
||||||
|
|
||||||
|
UnstyledButton(
|
||||||
|
onClick = onClick,
|
||||||
|
contentPadding = PaddingValues(0.dp),
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
indication = null,
|
||||||
|
modifier = modifier
|
||||||
|
.scale(scale)
|
||||||
|
.size(size),
|
||||||
|
backgroundColor = tint,
|
||||||
|
borderColor = borderTint,
|
||||||
|
borderWidth = borderWidth,
|
||||||
|
shape = CircleShape,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
UnstyledIcon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
tint = iconTint,
|
||||||
|
modifier = Modifier.size(iconSize),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
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.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
@@ -13,7 +12,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -31,14 +29,14 @@ import androidx.compose.ui.unit.sp
|
|||||||
import com.composables.icons.lucide.Calendar
|
import com.composables.icons.lucide.Calendar
|
||||||
import com.composables.icons.lucide.Clock
|
import com.composables.icons.lucide.Clock
|
||||||
import com.composables.icons.lucide.Lucide
|
import com.composables.icons.lucide.Lucide
|
||||||
import com.composeunstyled.UnstyledButton
|
|
||||||
import com.composeunstyled.UnstyledIcon
|
import com.composeunstyled.UnstyledIcon
|
||||||
|
import dev.ulfrx.recipe.ui.components.button.CircleButton
|
||||||
import dev.ulfrx.recipe.ui.theme.RecipeTheme
|
import dev.ulfrx.recipe.ui.theme.RecipeTheme
|
||||||
import org.jetbrains.compose.resources.painterResource
|
import org.jetbrains.compose.resources.painterResource
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import recipe.composeapp.generated.resources.Res
|
import recipe.composeapp.generated.resources.Res
|
||||||
|
import recipe.composeapp.generated.resources.meal_plan_editor_title_a11y
|
||||||
import recipe.composeapp.generated.resources.recipe_card_minutes_format
|
import recipe.composeapp.generated.resources.recipe_card_minutes_format
|
||||||
import recipe.composeapp.generated.resources.recipe_detail_plan_button
|
|
||||||
import recipe.composeapp.generated.resources.sample_recipe
|
import recipe.composeapp.generated.resources.sample_recipe
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -83,6 +81,12 @@ internal fun RecipeDetailHero(
|
|||||||
|
|
||||||
Spacer(Modifier.height(spacing.lg))
|
Spacer(Modifier.height(spacing.lg))
|
||||||
|
|
||||||
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
horizontalAlignment = Alignment.Start,
|
||||||
|
verticalArrangement =Arrangement.spacedBy(spacing.lg),
|
||||||
|
) {
|
||||||
BasicText(
|
BasicText(
|
||||||
text = title,
|
text = title,
|
||||||
style =
|
style =
|
||||||
@@ -91,30 +95,17 @@ internal fun RecipeDetailHero(
|
|||||||
fontSize = TITLE_FONT_SIZE,
|
fontSize = TITLE_FONT_SIZE,
|
||||||
lineHeight = TITLE_LINE_HEIGHT,
|
lineHeight = TITLE_LINE_HEIGHT,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Left,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(spacing.sm)) {
|
||||||
Spacer(Modifier.height(spacing.lg))
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(spacing.sm),
|
|
||||||
) {
|
|
||||||
MetaChip(
|
MetaChip(
|
||||||
icon = Lucide.Clock,
|
icon = Lucide.Clock,
|
||||||
text = stringResource(Res.string.recipe_card_minutes_format, cookingMinutes),
|
text = stringResource(Res.string.recipe_card_minutes_format, cookingMinutes),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
PlanButton(
|
}
|
||||||
text = stringResource(Res.string.recipe_detail_plan_button),
|
PlanButton(onClick = onPlanClick)
|
||||||
onClick = onPlanClick,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,33 +141,19 @@ private fun MetaChip(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PlanButton(
|
private fun PlanButton(
|
||||||
text: String,
|
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val colors = RecipeTheme.colors
|
CircleButton(
|
||||||
UnstyledButton(
|
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
shape = CHIP_SHAPE,
|
icon = Lucide.Calendar,
|
||||||
backgroundColor = colors.accent,
|
contentDescription = stringResource(Res.string.meal_plan_editor_title_a11y),
|
||||||
contentColor = colors.surface,
|
size = PLAN_BUTTON_SIZE,
|
||||||
contentPadding = PaddingValues(horizontal = PLAN_PADDING_H, vertical = PLAN_PADDING_V),
|
iconSize = PLAN_BUTTON_ICON_SIZE,
|
||||||
) {
|
tint = RecipeTheme.colors.surface,
|
||||||
UnstyledIcon(
|
iconTint = RecipeTheme.colors.accent,
|
||||||
imageVector = Lucide.Calendar,
|
borderTint = RecipeTheme.colors.borderCard,
|
||||||
contentDescription = null,
|
borderWidth = 1.dp,
|
||||||
tint = colors.surface,
|
|
||||||
modifier = Modifier.size(CHIP_ICON_SIZE),
|
|
||||||
)
|
)
|
||||||
Spacer(Modifier.width(CHIP_ICON_GAP))
|
|
||||||
BasicText(
|
|
||||||
text = text,
|
|
||||||
style =
|
|
||||||
RecipeTheme.typography.label.copy(
|
|
||||||
color = colors.surface,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val BANNER_ASPECT_RATIO = 16f / 9f
|
private const val BANNER_ASPECT_RATIO = 16f / 9f
|
||||||
@@ -187,15 +164,13 @@ private val BANNER_SHADOW_COLOR = Color.Black.copy(alpha = 0.45f)
|
|||||||
// Leave room for the sheet handle (8dp top padding + 5dp handle) plus breathing room.
|
// Leave room for the sheet handle (8dp top padding + 5dp handle) plus breathing room.
|
||||||
private val HERO_TOP_PADDING = 32.dp
|
private val HERO_TOP_PADDING = 32.dp
|
||||||
|
|
||||||
private val TITLE_FONT_SIZE = 24.sp
|
private val TITLE_FONT_SIZE = 19.sp
|
||||||
private val TITLE_LINE_HEIGHT = 28.sp
|
private val TITLE_LINE_HEIGHT = 20.sp
|
||||||
|
|
||||||
private val CHIP_SHAPE = RoundedCornerShape(percent = 50)
|
private val CHIP_SHAPE = RoundedCornerShape(percent = 50)
|
||||||
private val CHIP_PADDING_H = 12.dp
|
private val CHIP_PADDING_H = 12.dp
|
||||||
private val CHIP_PADDING_V = 7.dp
|
private val CHIP_PADDING_V = 7.dp
|
||||||
private val CHIP_ICON_SIZE = 14.dp
|
private val CHIP_ICON_SIZE = 14.dp
|
||||||
private val CHIP_ICON_GAP = 5.dp
|
private val CHIP_ICON_GAP = 5.dp
|
||||||
|
private val PLAN_BUTTON_SIZE = 50.dp
|
||||||
// Plan button is slightly more padded than meta chips so it reads as a CTA, not just a coloured chip.
|
private val PLAN_BUTTON_ICON_SIZE = 25.dp
|
||||||
private val PLAN_PADDING_H = 14.dp
|
|
||||||
private val PLAN_PADDING_V = 9.dp
|
|
||||||
|
|||||||
Reference in New Issue
Block a user