diff --git a/Jetsnack/app/build.gradle.kts b/Jetsnack/app/build.gradle.kts index 0fca21037..2d1d6a086 100644 --- a/Jetsnack/app/build.gradle.kts +++ b/Jetsnack/app/build.gradle.kts @@ -86,6 +86,7 @@ android { kotlin { compilerOptions { jvmTarget = JvmTarget.fromTarget("17") + freeCompilerArgs.add("-opt-in=androidx.compose.foundation.style.ExperimentalFoundationStyleApi") } } compileOptions { diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Button.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Button.kt index 794bd3f1d..b5fc023b3 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Button.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Button.kt @@ -14,102 +14,76 @@ * limitations under the License. */ +@file:OptIn(ExperimentalFoundationStyleApi::class) + package com.example.jetsnack.ui.components import android.content.res.Configuration.UI_MODE_NIGHT_YES -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.rememberUpdatedStyleState +import androidx.compose.foundation.style.then import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ProvideTextStyle -import androidx.compose.material3.Text import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.graphics.Shape import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview import com.example.jetsnack.ui.theme.JetsnackTheme @Composable -fun JetsnackButton( +fun Button( onClick: () -> Unit, modifier: Modifier = Modifier, + style: Style = Style, enabled: Boolean = true, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - shape: Shape = ButtonShape, - border: BorderStroke? = null, - backgroundGradient: List = JetsnackTheme.colors.interactivePrimary, - disabledBackgroundGradient: List = JetsnackTheme.colors.interactiveSecondary, - contentColor: Color = JetsnackTheme.colors.textInteractive, - disabledContentColor: Color = JetsnackTheme.colors.textHelp, - contentPadding: PaddingValues = ButtonDefaults.ContentPadding, content: @Composable RowScope.() -> Unit, ) { - JetsnackSurface( - shape = shape, - color = Color.Transparent, - contentColor = if (enabled) contentColor else disabledContentColor, - border = border, + val styleState = rememberUpdatedStyleState(interactionSource, { + it.isEnabled = enabled + }) + Surface( + style = JetsnackTheme.appStyles.buttonStyle then style, + styleState = styleState, modifier = modifier - .clip(shape) - .background( - Brush.horizontalGradient( - colors = if (enabled) backgroundGradient else disabledBackgroundGradient, - ), - ) .clickable( onClick = onClick, enabled = enabled, role = Role.Button, interactionSource = interactionSource, - indication = null, + indication = ripple() //TODO This ripple doesn't know the shape of the button. ), ) { - ProvideTextStyle( - value = MaterialTheme.typography.labelLarge, - ) { - Row( - Modifier - .defaultMinSize( - minWidth = ButtonDefaults.MinWidth, - minHeight = ButtonDefaults.MinHeight, - ) - .indication(interactionSource, ripple()) - .padding(contentPadding), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - content = content, - ) - } + Row( + Modifier + .defaultMinSize( + minWidth = ButtonDefaults.MinWidth, + minHeight = ButtonDefaults.MinHeight, + ), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + content = content, + ) } } -private val ButtonShape = RoundedCornerShape(percent = 50) - @Preview("default", "round") @Preview("dark theme", "round", uiMode = UI_MODE_NIGHT_YES) @Preview("large font", "round", fontScale = 2f) @Composable private fun ButtonPreview() { JetsnackTheme { - JetsnackButton(onClick = {}) { + Button(onClick = {}) { Text(text = "Demo") } } @@ -121,8 +95,10 @@ private fun ButtonPreview() { @Composable private fun RectangleButtonPreview() { JetsnackTheme { - JetsnackButton( - onClick = {}, shape = RectangleShape, + Button( + onClick = {}, style = { + shape(RectangleShape) + }, ) { Text(text = "Demo") } diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Card.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Card.kt index 850747216..dc8c4041e 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Card.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Card.kt @@ -14,39 +14,26 @@ * limitations under the License. */ +@file:OptIn(ExperimentalFoundationStyleApi::class) + package com.example.jetsnack.ui.components import android.content.res.Configuration -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.then import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.theme.JetsnackTheme @Composable -fun JetsnackCard( - modifier: Modifier = Modifier, - shape: Shape = MaterialTheme.shapes.medium, - color: Color = JetsnackTheme.colors.uiBackground, - contentColor: Color = JetsnackTheme.colors.textPrimary, - border: BorderStroke? = null, - elevation: Dp = 4.dp, - content: @Composable () -> Unit, -) { - JetsnackSurface( +fun JetsnackCard(modifier: Modifier = Modifier, style: Style = Style, content: @Composable () -> Unit) { + Surface( modifier = modifier, - shape = shape, - color = color, - contentColor = contentColor, - elevation = elevation, - border = border, + style = JetsnackTheme.appStyles.cardStyle then style, content = content, ) } diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Divider.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Divider.kt index 089313675..8a14dcb80 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Divider.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Divider.kt @@ -14,36 +14,30 @@ * limitations under the License. */ +@file:OptIn(ExperimentalFoundationStyleApi::class) + package com.example.jetsnack.ui.components import android.content.res.Configuration import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.styleable import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.JetsnackTheme.Companion.LocalJetsnackTheme @Composable -fun JetsnackDivider( - modifier: Modifier = Modifier, - color: Color = JetsnackTheme.colors.uiBorder.copy(alpha = DividerAlpha), - thickness: Dp = 1.dp, -) { - HorizontalDivider( - modifier = modifier, - color = color, - thickness = thickness, - ) +fun JetsnackDivider(modifier: Modifier = Modifier, style: Style = Style) { + Box(modifier = modifier.styleable(null, LocalJetsnackTheme.current.appStyles.dividerStyle, style)) } -private const val DividerAlpha = 0.12f - @Preview("default", showBackground = true) @Preview("dark theme", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) @Composable diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Filters.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Filters.kt index 30121ea10..58dc08f5a 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Filters.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Filters.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(ExperimentalSharedTransitionApi::class) +@file:OptIn(ExperimentalSharedTransitionApi::class, ExperimentalFoundationStyleApi::class) package com.example.jetsnack.ui.components @@ -22,10 +22,7 @@ import android.content.res.Configuration import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope -import androidx.compose.animation.animateColorAsState -import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues @@ -35,17 +32,21 @@ import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.selection.toggleable import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.pressed +import androidx.compose.foundation.style.rememberUpdatedStyleState +import androidx.compose.foundation.style.styleable +import androidx.compose.foundation.style.then import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text 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.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -54,6 +55,9 @@ import com.example.jetsnack.R import com.example.jetsnack.model.Filter import com.example.jetsnack.ui.FilterSharedElementKey import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.shapes +import com.example.jetsnack.ui.theme.typography @Composable fun FilterBar( @@ -93,63 +97,64 @@ fun FilterBar( } } items(filters) { filter -> - FilterChip(filter = filter, shape = MaterialTheme.shapes.small) + FilterChip( + filter = filter, + style = Style { + shape(shapes.small) + }, + ) } } } } @Composable -fun FilterChip(filter: Filter, modifier: Modifier = Modifier, shape: Shape = MaterialTheme.shapes.small) { +fun FilterChip(filter: Filter, modifier: Modifier = Modifier, style: Style = Style) { + val (selected, setSelected) = filter.enabled - val backgroundColor by animateColorAsState( - if (selected) JetsnackTheme.colors.brandSecondary else JetsnackTheme.colors.uiBackground, - label = "background color", - ) - val border = Modifier.fadeInDiagonalGradientBorder( - showBorder = !selected, - colors = JetsnackTheme.colors.interactiveSecondary, - shape = shape, - ) - val textColor by animateColorAsState( - if (selected) Color.Black else JetsnackTheme.colors.textSecondary, - label = "text color", + val interactionSource = remember { MutableInteractionSource() } + val styleState = rememberUpdatedStyleState( + interactionSource, + { + it.isSelected = selected + }, ) - JetsnackSurface( - modifier = modifier, - color = backgroundColor, - contentColor = textColor, - shape = shape, - elevation = 2.dp, + Surface( + modifier = modifier + .toggleable( + value = selected, + onValueChange = setSelected, + interactionSource = interactionSource, + indication = null, + ), + style = JetsnackTheme.appStyles.filterChipStyle then style, + styleState = styleState, ) { - val interactionSource = remember { MutableInteractionSource() } - - val pressed by interactionSource.collectIsPressedAsState() - val backgroundPressed = - if (pressed) { - Modifier.offsetGradientBackground( - JetsnackTheme.colors.interactiveSecondary, - 200f, - 0f, - ) - } else { - Modifier.background(Color.Transparent) + val innerBackgroundStyle = Style { + background(Color.Transparent) + pressed { + animate { + background( + Brush.horizontalGradient( + colors = colors.interactiveSecondary, + startX = 0f, + endX = 200f, + tileMode = TileMode.Mirror, + ), + ) + } } + } Box( modifier = Modifier - .toggleable( - value = selected, - onValueChange = setSelected, - interactionSource = interactionSource, - indication = null, - ) - .then(backgroundPressed) - .then(border), + .styleable(styleState, innerBackgroundStyle), ) { Text( text = filter.name, - style = MaterialTheme.typography.bodySmall, + style = { + textStyleWithFontFamilyFix(typography.bodySmall) + }, maxLines = 1, modifier = Modifier.padding( horizontal = 20.dp, diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Gradient.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Gradient.kt index 1a7fba80c..5709b8cec 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Gradient.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Gradient.kt @@ -16,11 +16,8 @@ package com.example.jetsnack.ui.components -import androidx.compose.animation.animateColorAsState -import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.ui.Modifier -import androidx.compose.ui.composed import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.BlendMode @@ -32,7 +29,7 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -fun Modifier.diagonalGradientTint(colors: List, blendMode: BlendMode) = drawWithContent { +fun Modifier.contentTintDiagonalGradient(colors: List, blendMode: BlendMode) = drawWithContent { drawContent() drawRect( brush = Brush.linearGradient(colors), @@ -40,15 +37,6 @@ fun Modifier.diagonalGradientTint(colors: List, blendMode: BlendMode) = d ) } -fun Modifier.offsetGradientBackground(colors: List, width: Float, offset: Float = 0f) = background( - Brush.horizontalGradient( - colors = colors, - startX = -offset, - endX = width - offset, - tileMode = TileMode.Mirror, - ), -) - fun Modifier.offsetGradientBackground(colors: List, width: Density.() -> Float, offset: Density.() -> Float = { 0f }) = drawBehind { val actualOffset = offset() @@ -67,17 +55,3 @@ fun Modifier.diagonalGradientBorder(colors: List, borderSize: Dp = 2.dp, brush = Brush.linearGradient(colors), shape = shape, ) - -fun Modifier.fadeInDiagonalGradientBorder(showBorder: Boolean, colors: List, borderSize: Dp = 2.dp, shape: Shape) = composed { - val animatedColors = List(colors.size) { i -> - animateColorAsState( - if (showBorder) colors[i] else colors[i].copy(alpha = 0f), - label = "animated color", - ).value - } - diagonalGradientBorder( - colors = animatedColors, - borderSize = borderSize, - shape = shape, - ) -} diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/GradientTintedIconButton.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/GradientTintedIconButton.kt index 39ebb9070..0b73d55bd 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/GradientTintedIconButton.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/GradientTintedIconButton.kt @@ -14,23 +14,29 @@ * limitations under the License. */ +@file:OptIn(ExperimentalFoundationStyleApi::class) + package com.example.jetsnack.ui.components import android.content.res.Configuration import androidx.annotation.DrawableRes -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.pressed +import androidx.compose.foundation.style.rememberUpdatedStyleState +import androidx.compose.foundation.style.styleable import androidx.compose.material3.Icon import androidx.compose.material3.Surface +import androidx.compose.material3.Text 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.clip import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource @@ -39,43 +45,20 @@ import androidx.compose.ui.unit.dp import com.example.jetsnack.R import com.example.jetsnack.ui.theme.JetsnackTheme +// todo introduce lint check for when style is provided but not used ? @Composable fun JetsnackGradientTintedIconButton( @DrawableRes iconResourceId: Int, onClick: () -> Unit, contentDescription: String?, modifier: Modifier = Modifier, + style: Style = Style, + // TODO we cannot migrate this out yet!! colors: List = JetsnackTheme.colors.interactiveSecondary, ) { val interactionSource = remember { MutableInteractionSource() } - // This should use a layer + srcIn but needs investigation - val border = Modifier.fadeInDiagonalGradientBorder( - showBorder = true, - colors = JetsnackTheme.colors.interactiveSecondary, - shape = CircleShape, - ) - val pressed by interactionSource.collectIsPressedAsState() - val background = if (pressed) { - Modifier.offsetGradientBackground(colors, 200f, 0f) - } else { - Modifier.background(JetsnackTheme.colors.uiBackground) - } - val blendMode = if (JetsnackTheme.colors.isDark) BlendMode.Darken else BlendMode.Plus - val modifierColor = if (pressed) { - Modifier.diagonalGradientTint( - colors = listOf( - JetsnackTheme.colors.textSecondary, - JetsnackTheme.colors.textSecondary, - ), - blendMode = blendMode, - ) - } else { - Modifier.diagonalGradientTint( - colors = colors, - blendMode = blendMode, - ) - } + val styleState = rememberUpdatedStyleState(interactionSource) Surface( modifier = modifier .clickable( @@ -83,11 +66,27 @@ fun JetsnackGradientTintedIconButton( interactionSource = interactionSource, indication = null, ) - .clip(CircleShape) - .then(border) - .then(background), + .styleable(styleState, JetsnackTheme.appStyles.gradientIconButtonStyle, style), color = Color.Transparent, ) { + val blendMode = if (JetsnackTheme.colors.isDark) BlendMode.Darken else BlendMode.Plus + // todo this cant be migrated yet due to no support for blendMode and drawWithContent in Styles + // This should use a layer + srcIn but needs investigation + val pressed = interactionSource.collectIsPressedAsState().value + val modifierColor = if (pressed) { + Modifier.contentTintDiagonalGradient( + colors = listOf( + JetsnackTheme.colors.textPrimary, + JetsnackTheme.colors.textPrimary, + ), + blendMode = blendMode, + ) + } else { + Modifier.contentTintDiagonalGradient( + colors = colors, + blendMode = blendMode, + ) + } Icon( painter = painterResource(id = iconResourceId), contentDescription = contentDescription, @@ -108,4 +107,4 @@ private fun GradientTintedIconButtonPreview() { modifier = Modifier.padding(4.dp), ) } -} +} \ No newline at end of file diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/QuantitySelector.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/QuantitySelector.kt index 4952bd276..6940051e9 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/QuantitySelector.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/QuantitySelector.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalFoundationStyleApi::class) + package com.example.jetsnack.ui.components import android.content.res.Configuration.UI_MODE_NIGHT_YES @@ -21,8 +23,7 @@ import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment @@ -37,15 +38,19 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.example.jetsnack.R import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.typography @Composable fun QuantitySelector(count: Int, decreaseItemCount: () -> Unit, increaseItemCount: () -> Unit, modifier: Modifier = Modifier) { Row(modifier = modifier) { Text( text = stringResource(R.string.quantity), - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textSecondary, - fontWeight = FontWeight.Normal, + style = { + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textSecondary) + fontWeight(FontWeight.Normal) + }, modifier = Modifier .padding(end = 18.dp) .align(Alignment.CenterVertically), @@ -63,10 +68,12 @@ fun QuantitySelector(count: Int, decreaseItemCount: () -> Unit, increaseItemCoun ) { Text( text = "$it", - style = MaterialTheme.typography.titleSmall, - fontSize = 18.sp, - color = JetsnackTheme.colors.textPrimary, - textAlign = TextAlign.Center, + style = { + textStyleWithFontFamilyFix(typography.titleSmall) + fontSize(18.sp) + contentColor(colors.textPrimary) + textAlign(TextAlign.Center) + }, modifier = Modifier.widthIn(min = 24.dp), ) } @@ -85,7 +92,7 @@ fun QuantitySelector(count: Int, decreaseItemCount: () -> Unit, increaseItemCoun @Composable fun QuantitySelectorPreview() { JetsnackTheme { - JetsnackSurface { + Surface { QuantitySelector(1, {}, {}) } } @@ -95,7 +102,7 @@ fun QuantitySelectorPreview() { @Composable fun QuantitySelectorPreviewRtl() { JetsnackTheme { - JetsnackSurface { + Surface { CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { QuantitySelector(1, {}, {}) } diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Snacks.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Snacks.kt index 028ee2b1c..b77f6935f 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Snacks.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Snacks.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(ExperimentalSharedTransitionApi::class) +@file:OptIn(ExperimentalSharedTransitionApi::class, ExperimentalFoundationStyleApi::class) package com.example.jetsnack.ui.components @@ -49,10 +49,10 @@ 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.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.Style import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -83,6 +83,9 @@ import com.example.jetsnack.ui.SnackSharedElementType import com.example.jetsnack.ui.snackdetail.nonSpatialExpressiveSpring import com.example.jetsnack.ui.snackdetail.snackDetailBoundsTransform import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.shapes +import com.example.jetsnack.ui.theme.typography private val HighlightCardWidth = 170.dp private val HighlightCardPadding = 16.dp @@ -106,8 +109,10 @@ fun SnackCollection( ) { Text( text = snackCollection.name, - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.brand, + style = { + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.brand) + }, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier @@ -188,8 +193,10 @@ private fun Snacks(snackCollectionId: Long, snacks: List, onSnackClick: ( @Composable fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier) { - JetsnackSurface( - shape = MaterialTheme.shapes.medium, + Surface( + style = { + shape(shapes.medium) + }, modifier = modifier.padding( start = 4.dp, end = 4.dp, @@ -231,8 +238,10 @@ fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String ) Text( text = snack.name, - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textSecondary, + style = { + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textSecondary) + }, modifier = Modifier .padding(top = 8.dp) .wrapContentWidth() @@ -280,8 +289,9 @@ private fun HighlightSnackItem( } } JetsnackCard( - elevation = 0.dp, - shape = RoundedCornerShape(roundedCornerAnimation), + style = Style { + shape(RoundedCornerShape(roundedCornerAnimation)) + }, modifier = modifier .padding(bottom = 16.dp) .sharedBounds( @@ -389,8 +399,10 @@ private fun HighlightSnackItem( text = snack.name, maxLines = 1, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.textSecondary, + style = { + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.textSecondary) + }, modifier = Modifier .padding(horizontal = 16.dp) .sharedBounds( @@ -413,8 +425,10 @@ private fun HighlightSnackItem( Text( text = snack.tagline, - style = MaterialTheme.typography.bodyLarge, - color = JetsnackTheme.colors.textHelp, + style = { + textStyleWithFontFamilyFix(typography.bodyLarge) + contentColor(colors.textHelp) + }, modifier = Modifier .padding(horizontal = 16.dp) .sharedBounds( @@ -453,12 +467,13 @@ fun SnackImage( modifier: Modifier = Modifier, elevation: Dp = 0.dp, ) { - JetsnackSurface( - elevation = elevation, - shape = CircleShape, + Surface( + style = { + shape(CircleShape) + // todo elevation + }, modifier = modifier, ) { - AsyncImage( model = ImageRequest.Builder(LocalContext.current) .data(imageRes) diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Surface.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Surface.kt index 1104c51f1..fbbfd30f7 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Surface.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Surface.kt @@ -14,85 +14,37 @@ * limitations under the License. */ +@file:OptIn(ExperimentalFoundationStyleApi::class) + package com.example.jetsnack.ui.components -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box -import androidx.compose.material3.LocalContentColor +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.StyleState +import androidx.compose.foundation.style.rememberUpdatedStyleState +import androidx.compose.foundation.style.styleable import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.shadow -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.compositeOver -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.zIndex import com.example.jetsnack.ui.theme.JetsnackTheme -import kotlin.math.ln /** * An alternative to [androidx.compose.material3.Surface] utilizing * [com.example.jetsnack.ui.theme.JetsnackColors] */ @Composable -fun JetsnackSurface( +fun Surface( modifier: Modifier = Modifier, - shape: Shape = RectangleShape, - color: Color = JetsnackTheme.colors.uiBackground, - contentColor: Color = JetsnackTheme.colors.textSecondary, - border: BorderStroke? = null, - elevation: Dp = 0.dp, + style: Style = Style, + // todo confirm patten is acceptable + styleState: StyleState = rememberUpdatedStyleState(null), content: @Composable () -> Unit, ) { Box( modifier = modifier - .shadow(elevation = elevation, shape = shape, clip = false) - .zIndex(elevation.value) - .then(if (border != null) Modifier.border(border, shape) else Modifier) - .background( - color = getBackgroundColorForElevation(color, elevation), - shape = shape, - ) - .clip(shape), - ) { - CompositionLocalProvider(LocalContentColor provides contentColor, content = content) - } -} - -@Composable -private fun getBackgroundColorForElevation(color: Color, elevation: Dp): Color { - return if (elevation > 0.dp // && https://issuetracker.google.com/issues/161429530 - // JetsnackTheme.colors.isDark //&& - // color == JetsnackTheme.colors.uiBackground + .styleable(styleState, JetsnackTheme.appStyles.surfaceStyle, style) ) { - color.withElevation(elevation) - } else { - color + //todo double check CompositionLocalProvider(LocalContentColor provides contentColor, content = content) + content() } } - -/** - * Applies a [Color.White] overlay to this color based on the [elevation]. This increases visibility - * of elevation for surfaces in a dark theme. - * - * TODO: Remove when public https://issuetracker.google.com/155181601 - */ -private fun Color.withElevation(elevation: Dp): Color { - val foreground = calculateForeground(elevation) - return foreground.compositeOver(this) -} - -/** - * @return the alpha-modified [Color.White] to overlay on top of the surface color to produce - * the resultant color. - */ -private fun calculateForeground(elevation: Dp): Color { - val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f - return Color.White.copy(alpha = alpha) -} diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Text.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Text.kt new file mode 100644 index 000000000..f0e374b16 --- /dev/null +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Text.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.jetsnack.ui.components + +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.StyleScope +import androidx.compose.foundation.style.styleable +import androidx.compose.foundation.text.BasicText +import androidx.compose.foundation.text.TextAutoSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextOverflow +import com.example.jetsnack.ui.theme.JetsnackTheme + +// Workaround for b/492528450 - setting textStyle currently doesn't set fontFamily. +@ExperimentalFoundationStyleApi +fun StyleScope.textStyleWithFontFamilyFix(value: TextStyle) { + textStyle(value) + value.fontFamily?.let { fontFamily(it) } +} + +@ExperimentalFoundationStyleApi +@Composable +fun Text( + text: String, + modifier: Modifier = Modifier, + style: Style = Style, + onTextLayout: ((TextLayoutResult) -> Unit)? = null, + overflow: TextOverflow = TextOverflow.Clip, + softWrap: Boolean = true, + maxLines: Int = Int.MAX_VALUE, + minLines: Int = 1, + autoSize: TextAutoSize? = null, +) { + BasicText( + text = text, + modifier = modifier.styleable(null, JetsnackTheme.appStyles.defaultTextStyle, style), + onTextLayout = onTextLayout, + overflow = overflow, + softWrap = softWrap, + maxLines = maxLines, + minLines = minLines, + autoSize = autoSize, + ) +} diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/DestinationBar.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/DestinationBar.kt index b706ea8bb..b5c85d0df 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/DestinationBar.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/DestinationBar.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(ExperimentalSharedTransitionApi::class) +@file:OptIn(ExperimentalSharedTransitionApi::class, ExperimentalFoundationStyleApi::class) package com.example.jetsnack.ui.home @@ -25,11 +25,10 @@ import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -45,9 +44,13 @@ import com.example.jetsnack.ui.LocalNavAnimatedVisibilityScope import com.example.jetsnack.ui.LocalSharedTransitionScope import com.example.jetsnack.ui.components.JetsnackDivider import com.example.jetsnack.ui.components.JetsnackPreviewWrapper +import com.example.jetsnack.ui.components.Text +import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix import com.example.jetsnack.ui.snackdetail.spatialExpressiveSpring import com.example.jetsnack.ui.theme.AlphaNearOpaque import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.typography @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -72,9 +75,11 @@ fun DestinationBar(modifier: Modifier = Modifier) { Row { Text( text = "Delivery to 1600 Amphitheater Way", - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textSecondary, - textAlign = TextAlign.Center, + style = { + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textSecondary) + textAlign(TextAlign.Center) + }, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Feed.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Feed.kt index 10e3125ce..4c9b29cc2 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Feed.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Feed.kt @@ -47,7 +47,7 @@ import com.example.jetsnack.model.SnackCollection import com.example.jetsnack.model.SnackRepo import com.example.jetsnack.ui.components.FilterBar import com.example.jetsnack.ui.components.JetsnackDivider -import com.example.jetsnack.ui.components.JetsnackSurface +import com.example.jetsnack.ui.components.Surface import com.example.jetsnack.ui.components.SnackCollection import com.example.jetsnack.ui.theme.JetsnackTheme @@ -70,7 +70,7 @@ private fun Feed( onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, ) { - JetsnackSurface(modifier = modifier.fillMaxSize()) { + Surface(modifier = modifier.fillMaxSize()) { var filtersVisible by remember { mutableStateOf(false) } @@ -124,7 +124,9 @@ private fun SnackCollectionList( } itemsIndexed(snackCollections) { index, snackCollection -> if (index > 0) { - JetsnackDivider(thickness = 2.dp) + JetsnackDivider(style = { + height(2.dp) + }) } SnackCollection( diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/FilterScreen.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/FilterScreen.kt index f2c0dcabc..0bf868481 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/FilterScreen.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/FilterScreen.kt @@ -49,7 +49,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf @@ -71,7 +70,11 @@ import com.example.jetsnack.model.Filter import com.example.jetsnack.model.SnackRepo import com.example.jetsnack.ui.FilterSharedElementKey import com.example.jetsnack.ui.components.FilterChip +import com.example.jetsnack.ui.components.Text +import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.typography @Composable fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope, onDismiss: () -> Unit) { @@ -139,8 +142,10 @@ fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilit .fillMaxWidth() .fillMaxHeight() .padding(top = 8.dp, end = 48.dp), - textAlign = TextAlign.Center, - style = MaterialTheme.typography.titleLarge, + style = { + textAlign(TextAlign.Center) + textStyleWithFontFamilyFix(typography.titleLarge) + }, ) val resetEnabled = sortState != defaultFilter @@ -156,10 +161,14 @@ fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilit Text( text = stringResource(id = R.string.reset), - style = MaterialTheme.typography.bodyMedium, - fontWeight = fontWeight, - color = JetsnackTheme.colors.uiBackground - .copy(alpha = if (!resetEnabled) 0.38f else 1f), + style = { + textStyleWithFontFamilyFix(typography.bodyMedium) + fontWeight(fontWeight) + contentColor( + colors.uiBackground + .copy(alpha = if (!resetEnabled) 0.38f else 1f), + ) + }, ) } } @@ -245,8 +254,10 @@ fun MaxCalories(sliderPosition: Float, onValueChanged: (Float) -> Unit) { FilterTitle(text = stringResource(id = R.string.max_calories)) Text( text = stringResource(id = R.string.per_serving), - style = MaterialTheme.typography.bodyMedium, - color = JetsnackTheme.colors.brand, + style = { + textStyleWithFontFamilyFix(typography.bodyMedium) + contentColor(colors.brand) + }, modifier = Modifier.padding(top = 5.dp, start = 10.dp), ) } @@ -271,8 +282,10 @@ fun MaxCalories(sliderPosition: Float, onValueChanged: (Float) -> Unit) { fun FilterTitle(text: String) { Text( text = text, - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.brand, + style = { + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.brand) + }, modifier = Modifier.padding(bottom = 8.dp), ) } @@ -289,7 +302,9 @@ fun SortOption(text: String, @DrawableRes icon: Int?, onClickOption: () -> Unit, } Text( text = text, - style = MaterialTheme.typography.titleMedium, + style = { + textStyleWithFontFamilyFix(typography.titleMedium) + }, modifier = Modifier .padding(start = 10.dp) .weight(1f), diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Home.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Home.kt index 28d1f4b92..d2265268b 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Home.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Home.kt @@ -41,8 +41,6 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect @@ -60,6 +58,7 @@ import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalLocale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -75,12 +74,15 @@ import androidx.navigation.compose.composable import androidx.navigation.navDeepLink import com.example.jetsnack.R import com.example.jetsnack.ui.LocalNavAnimatedVisibilityScope -import com.example.jetsnack.ui.components.JetsnackSurface +import com.example.jetsnack.ui.components.Surface +import com.example.jetsnack.ui.components.Text +import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix import com.example.jetsnack.ui.home.cart.Cart import com.example.jetsnack.ui.home.search.Search import com.example.jetsnack.ui.snackdetail.nonSpatialExpressiveSpring import com.example.jetsnack.ui.snackdetail.spatialExpressiveSpring import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.typography import java.util.Locale fun NavGraphBuilder.composableWithCompositionLocal( @@ -172,10 +174,12 @@ fun JetsnackBottomBar( val routes = remember { tabs.map { it.route } } val currentSection = tabs.first { it.route == currentRoute } - JetsnackSurface( + Surface( modifier = modifier, - color = color, - contentColor = contentColor, + style = { + background(color) + contentColor(contentColor) + }, ) { val springSpec = spatialExpressiveSpring() JetsnackBottomNavLayout( @@ -187,7 +191,7 @@ fun JetsnackBottomBar( ) { val configuration = LocalConfiguration.current val currentLocale: Locale = - ConfigurationCompat.getLocales(configuration).get(0) ?: Locale.getDefault() + ConfigurationCompat.getLocales(configuration).get(0) ?: LocalLocale.current.platformLocale tabs.forEach { section -> val selected = section == currentSection @@ -213,8 +217,10 @@ fun JetsnackBottomBar( text = { Text( text = text, - color = tint, - style = MaterialTheme.typography.labelLarge, + style = { + contentColor(tint) + textStyleWithFontFamilyFix(typography.labelLarge) + }, maxLines = 1, ) }, diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Profile.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Profile.kt index 799fa28f3..33e5d8ca8 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Profile.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Profile.kt @@ -25,8 +25,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -36,7 +34,12 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.example.jetsnack.R +import com.example.jetsnack.ui.components.Text +import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.shapes +import com.example.jetsnack.ui.theme.typography @Composable fun Profile(modifier: Modifier = Modifier) { @@ -54,15 +57,19 @@ fun Profile(modifier: Modifier = Modifier) { Spacer(Modifier.height(24.dp)) Text( text = stringResource(R.string.work_in_progress), - style = MaterialTheme.typography.titleMedium, - textAlign = TextAlign.Center, + style = { + textStyleWithFontFamilyFix(typography.titleMedium) + textAlign(TextAlign.Center) + }, modifier = Modifier.fillMaxWidth(), ) Spacer(Modifier.height(16.dp)) Text( text = stringResource(R.string.grab_beverage), - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center, + style = { + textStyleWithFontFamilyFix(typography.bodyMedium) + textAlign(TextAlign.Center) + }, modifier = Modifier.fillMaxWidth(), ) } diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/Cart.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/Cart.kt index 626854613..5576b38f0 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/Cart.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/Cart.kt @@ -45,9 +45,7 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -56,7 +54,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.LastBaseline -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -71,17 +69,20 @@ import com.example.jetsnack.R import com.example.jetsnack.model.OrderLine import com.example.jetsnack.model.SnackCollection import com.example.jetsnack.model.SnackRepo -import com.example.jetsnack.ui.components.JetsnackButton +import com.example.jetsnack.ui.components.Button import com.example.jetsnack.ui.components.JetsnackDivider -import com.example.jetsnack.ui.components.JetsnackSurface +import com.example.jetsnack.ui.components.Text import com.example.jetsnack.ui.components.QuantitySelector import com.example.jetsnack.ui.components.SnackCollection import com.example.jetsnack.ui.components.SnackImage +import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix import com.example.jetsnack.ui.home.DestinationBar import com.example.jetsnack.ui.snackdetail.nonSpatialExpressiveSpring import com.example.jetsnack.ui.snackdetail.spatialExpressiveSpring import com.example.jetsnack.ui.theme.AlphaNearOpaque import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.typography import com.example.jetsnack.ui.utils.formatPrice import kotlin.math.roundToInt @@ -114,7 +115,7 @@ fun Cart( onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, ) { - JetsnackSurface(modifier = modifier.fillMaxSize()) { + com.example.jetsnack.ui.components.Surface(modifier = modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) { CartContent( orderLines = orderLines, @@ -141,7 +142,7 @@ private fun CartContent( onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, ) { - val resources = LocalContext.current.resources + val resources = LocalResources.current val snackCountFormattedString = remember(orderLines.size, resources) { resources.getQuantityString( R.plurals.cart_order_count, @@ -159,8 +160,10 @@ private fun CartContent( ) Text( text = stringResource(R.string.cart_order_header, snackCountFormattedString), - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.brand, + style = { + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.brand) + }, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier @@ -264,16 +267,18 @@ private fun SwipeDismissItemBackground(progress: Float) { ) } /*Text opacity increases as the text is supposed to appear in - the screen*/ + the screen*/ val textAlpha by animateFloatAsState( if (progress > 0.5f) 1f else 0.5f, label = "text alpha", ) if (progress > 0.5f) { Text( text = stringResource(id = R.string.remove_item), - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.uiBackground, - textAlign = TextAlign.Center, + style = { + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.uiBackground) + textAlign(TextAlign.Center) + }, modifier = Modifier .graphicsLayer( alpha = textAlpha, @@ -321,8 +326,10 @@ fun CartItem( Row(modifier = Modifier.fillMaxWidth()) { Text( text = snack.name, - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textSecondary, + style = { + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textSecondary) + }, modifier = Modifier .weight(1f) .padding(top = 16.dp, end = 16.dp), @@ -340,8 +347,10 @@ fun CartItem( } Text( text = snack.tagline, - style = MaterialTheme.typography.bodyLarge, - color = JetsnackTheme.colors.textHelp, + style = { + textStyleWithFontFamilyFix(typography.bodyLarge) + contentColor(colors.textHelp) + }, modifier = Modifier.padding(end = 16.dp), ) Spacer(Modifier.height(8.dp)) @@ -350,8 +359,10 @@ fun CartItem( ) { Text( text = formatPrice(snack.price), - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textPrimary, + style = { + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textPrimary) + }, modifier = Modifier .weight(1f) .padding(end = 16.dp) @@ -375,8 +386,10 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi Column(modifier) { Text( text = stringResource(R.string.cart_summary_header), - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.brand, + style = { + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.brand) + }, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier @@ -387,7 +400,9 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi Row(modifier = Modifier.padding(horizontal = 24.dp)) { Text( text = stringResource(R.string.cart_subtotal_label), - style = MaterialTheme.typography.bodyLarge, + style = { + textStyleWithFontFamilyFix(typography.bodyLarge) + }, modifier = Modifier .weight(1f) .wrapContentWidth(Alignment.Start) @@ -395,14 +410,18 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi ) Text( text = formatPrice(subtotal), - style = MaterialTheme.typography.bodyLarge, + style = { + textStyleWithFontFamilyFix(typography.bodyLarge) + }, modifier = Modifier.alignBy(LastBaseline), ) } Row(modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) { Text( text = stringResource(R.string.cart_shipping_label), - style = MaterialTheme.typography.bodyLarge, + style = { + textStyleWithFontFamilyFix(typography.bodyLarge) + }, modifier = Modifier .weight(1f) .wrapContentWidth(Alignment.Start) @@ -410,7 +429,9 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi ) Text( text = formatPrice(shippingCosts), - style = MaterialTheme.typography.bodyLarge, + style = { + textStyleWithFontFamilyFix(typography.bodyLarge) + }, modifier = Modifier.alignBy(LastBaseline), ) } @@ -419,7 +440,9 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi Row(modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) { Text( text = stringResource(R.string.cart_total_label), - style = MaterialTheme.typography.bodyLarge, + style = { + textStyleWithFontFamilyFix(typography.bodyLarge) + }, modifier = Modifier .weight(1f) .padding(end = 16.dp) @@ -428,7 +451,9 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi ) Text( text = formatPrice(subtotal + shippingCosts), - style = MaterialTheme.typography.titleMedium, + style = { + textStyleWithFontFamilyFix(typography.titleMedium) + }, modifier = Modifier.alignBy(LastBaseline), ) } @@ -447,9 +472,11 @@ private fun CheckoutBar(modifier: Modifier = Modifier) { JetsnackDivider() Row { Spacer(Modifier.weight(1f)) - JetsnackButton( + Button( onClick = { /* todo */ }, - shape = RectangleShape, + style = { + shape(RectangleShape) + }, modifier = Modifier .padding(horizontal = 12.dp, vertical = 8.dp) .weight(1f), @@ -457,7 +484,9 @@ private fun CheckoutBar(modifier: Modifier = Modifier) { Text( text = stringResource(id = R.string.cart_checkout), modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Left, + style = { + textAlign(TextAlign.Left) + }, maxLines = 1, ) } diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Categories.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Categories.kt index dba32a002..f17b2eaee 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Categories.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Categories.kt @@ -30,8 +30,6 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -45,9 +43,13 @@ import androidx.compose.ui.unit.dp import com.example.jetsnack.R import com.example.jetsnack.model.SearchCategory import com.example.jetsnack.model.SearchCategoryCollection +import com.example.jetsnack.ui.components.Text import com.example.jetsnack.ui.components.SnackImage import com.example.jetsnack.ui.components.VerticalGrid +import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.typography import kotlin.math.max @Composable @@ -65,8 +67,10 @@ private fun SearchCategoryCollection(collection: SearchCategoryCollection, index Column(modifier) { Text( text = collection.name, - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.textPrimary, + style = { + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.textPrimary) + }, modifier = Modifier .heightIn(min = 56.dp) .padding(horizontal = 24.dp, vertical = 4.dp) @@ -105,8 +109,10 @@ private fun SearchCategory(category: SearchCategory, gradient: List, modi content = { Text( text = category.name, - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textSecondary, + style = { + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textSecondary) + }, modifier = Modifier .padding(4.dp) .padding(start = 8.dp), diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Results.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Results.kt index 05aebc129..14057014f 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Results.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Results.kt @@ -21,7 +21,6 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.clickable 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.fillMaxSize @@ -34,8 +33,6 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -47,11 +44,15 @@ import androidx.compose.ui.unit.dp import com.example.jetsnack.R import com.example.jetsnack.model.Snack import com.example.jetsnack.model.snacks -import com.example.jetsnack.ui.components.JetsnackButton +import com.example.jetsnack.ui.components.Button import com.example.jetsnack.ui.components.JetsnackDivider -import com.example.jetsnack.ui.components.JetsnackSurface +import com.example.jetsnack.ui.components.Surface +import com.example.jetsnack.ui.components.Text import com.example.jetsnack.ui.components.SnackImage +import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.typography import com.example.jetsnack.ui.utils.formatPrice @Composable @@ -59,8 +60,10 @@ fun SearchResults(searchResults: List, onSnackClick: (Long, String) -> Un Column { Text( text = stringResource(R.string.search_count, searchResults.size), - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.textPrimary, + style = { + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.textPrimary) + }, modifier = Modifier.padding(horizontal = 24.dp, vertical = 4.dp), ) LazyColumn { @@ -100,25 +103,33 @@ private fun SearchResult(snack: Snack, onSnackClick: (Long, String) -> Unit, sho ) { Text( text = snack.name, - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textSecondary, + style = { + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textSecondary) + }, ) Text( text = snack.tagline, - style = MaterialTheme.typography.bodyLarge, - color = JetsnackTheme.colors.textHelp, + style = { + textStyleWithFontFamilyFix(typography.bodyLarge) + contentColor(colors.textHelp) + }, ) Spacer(Modifier.height(8.dp)) Text( text = formatPrice(snack.price), - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textPrimary, + style = { + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textPrimary) + }, ) } - JetsnackButton( + Button( onClick = { /* todo */ }, - shape = CircleShape, - contentPadding = PaddingValues(0.dp), + style = { + shape(CircleShape) + contentPadding(0.dp) + }, modifier = Modifier.size(36.dp), ) { Icon( @@ -146,15 +157,19 @@ fun NoResults(query: String, modifier: Modifier = Modifier) { Spacer(Modifier.height(24.dp)) Text( text = stringResource(R.string.search_no_matches, query), - style = MaterialTheme.typography.titleMedium, - textAlign = TextAlign.Center, + style = { + textStyleWithFontFamilyFix(typography.titleMedium) + textAlign(TextAlign.Center) + }, modifier = Modifier.fillMaxWidth(), ) Spacer(Modifier.height(16.dp)) Text( text = stringResource(R.string.search_no_matches_retry), - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center, + style = { + textStyleWithFontFamilyFix(typography.bodyMedium) + textAlign(TextAlign.Center) + }, modifier = Modifier.fillMaxWidth(), ) } @@ -166,7 +181,7 @@ fun NoResults(query: String, modifier: Modifier = Modifier) { @Composable private fun SearchResultPreview() { JetsnackTheme { - JetsnackSurface { + Surface { SearchResult( snack = snacks[0], onSnackClick = { _, _ -> }, diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Search.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Search.kt index 33e84c51e..e284e7951 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Search.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Search.kt @@ -34,8 +34,6 @@ import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable @@ -59,12 +57,15 @@ import com.example.jetsnack.model.SearchSuggestionGroup import com.example.jetsnack.model.Snack import com.example.jetsnack.model.SnackRepo import com.example.jetsnack.ui.components.JetsnackDivider -import com.example.jetsnack.ui.components.JetsnackSurface +import com.example.jetsnack.ui.components.Surface +import com.example.jetsnack.ui.components.Text import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.shapes @Composable fun Search(onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, state: SearchState = rememberSearchState()) { - JetsnackSurface(modifier = modifier.fillMaxSize()) { + Surface(modifier = modifier.fillMaxSize()) { Column { Spacer(modifier = Modifier.statusBarsPadding()) SearchBar( @@ -169,10 +170,12 @@ private fun SearchBar( searching: Boolean, modifier: Modifier = Modifier, ) { - JetsnackSurface( - color = JetsnackTheme.colors.uiFloated, - contentColor = JetsnackTheme.colors.textSecondary, - shape = MaterialTheme.shapes.small, + Surface( + style = { + background(colors.uiFloated) + contentColor(colors.textSecondary) + shape(shapes.small) + }, modifier = modifier .fillMaxWidth() .height(56.dp) @@ -239,7 +242,9 @@ private fun SearchHint() { Spacer(Modifier.width(8.dp)) Text( text = stringResource(R.string.search_jetsnack), - color = JetsnackTheme.colors.textHelp, + style = { + contentColor(colors.textHelp) + }, ) } } @@ -250,7 +255,7 @@ private fun SearchHint() { @Composable private fun SearchBarPreview() { JetsnackTheme { - JetsnackSurface { + Surface { SearchBar( query = TextFieldValue(""), onQueryChange = { }, diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Suggestions.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Suggestions.kt index 62b9ff40c..8ebe6ba34 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Suggestions.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Suggestions.kt @@ -26,8 +26,6 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -35,8 +33,12 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.example.jetsnack.model.SearchRepo import com.example.jetsnack.model.SearchSuggestionGroup -import com.example.jetsnack.ui.components.JetsnackSurface +import com.example.jetsnack.ui.components.Surface +import com.example.jetsnack.ui.components.Text +import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.typography @Composable fun SearchSuggestions(suggestions: List, onSuggestionSelect: (String) -> Unit) { @@ -63,8 +65,10 @@ fun SearchSuggestions(suggestions: List, onSuggestionSele private fun SuggestionHeader(name: String, modifier: Modifier = Modifier) { Text( text = name, - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.textPrimary, + style = { + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.textPrimary) + }, modifier = modifier .heightIn(min = 56.dp) .padding(horizontal = 24.dp, vertical = 4.dp) @@ -76,7 +80,9 @@ private fun SuggestionHeader(name: String, modifier: Modifier = Modifier) { private fun Suggestion(suggestion: String, onSuggestionSelect: (String) -> Unit, modifier: Modifier = Modifier) { Text( text = suggestion, - style = MaterialTheme.typography.titleMedium, + style = { + textStyleWithFontFamilyFix(typography.titleMedium) + }, modifier = modifier .heightIn(min = 48.dp) .clickable { onSuggestionSelect(suggestion) } @@ -91,7 +97,7 @@ private fun Suggestion(suggestion: String, onSuggestionSelect: (String) -> Unit, @Composable fun PreviewSuggestions() { JetsnackTheme { - JetsnackSurface { + Surface { SearchSuggestions( suggestions = SearchRepo.getSuggestions(), onSuggestionSelect = { }, diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/snackdetail/SnackDetail.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/snackdetail/SnackDetail.kt index 32cf6dd0c..ec555a7ab 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/snackdetail/SnackDetail.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/snackdetail/SnackDetail.kt @@ -61,11 +61,10 @@ import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.style.fillWidth import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.key @@ -103,15 +102,19 @@ import com.example.jetsnack.ui.LocalNavAnimatedVisibilityScope import com.example.jetsnack.ui.LocalSharedTransitionScope import com.example.jetsnack.ui.SnackSharedElementKey import com.example.jetsnack.ui.SnackSharedElementType -import com.example.jetsnack.ui.components.JetsnackButton +import com.example.jetsnack.ui.components.Button import com.example.jetsnack.ui.components.JetsnackDivider import com.example.jetsnack.ui.components.JetsnackPreviewWrapper -import com.example.jetsnack.ui.components.JetsnackSurface +import com.example.jetsnack.ui.components.Surface +import com.example.jetsnack.ui.components.Text import com.example.jetsnack.ui.components.QuantitySelector import com.example.jetsnack.ui.components.SnackCollection import com.example.jetsnack.ui.components.SnackImage +import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.theme.Neutral8 +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.typography import com.example.jetsnack.ui.utils.formatPrice import kotlin.math.max import kotlin.math.min @@ -296,7 +299,7 @@ private fun Body(related: List, scroll: ScrollState) { ) { Spacer(Modifier.height(GradientScroll)) Spacer(Modifier.height(ImageOverlap)) - JetsnackSurface( + Surface( Modifier .fillMaxWidth() .padding(top = 16.dp), @@ -305,21 +308,25 @@ private fun Body(related: List, scroll: ScrollState) { Spacer(Modifier.height(TitleHeight)) Text( text = stringResource(R.string.detail_header), - style = MaterialTheme.typography.labelSmall, - color = JetsnackTheme.colors.textHelp, - modifier = HzPadding, + style = { + textStyleWithFontFamilyFix(typography.headlineMedium) + contentColor(colors.textHelp) + contentPaddingHorizontal(24.dp) + }, ) Spacer(Modifier.height(16.dp)) var seeMore by remember { mutableStateOf(true) } with(sharedTransitionScope) { Text( text = stringResource(R.string.detail_placeholder), - style = MaterialTheme.typography.bodyLarge, - color = JetsnackTheme.colors.textHelp, + style = { + textStyleWithFontFamilyFix(typography.bodyLarge) + contentColor(colors.textHelp) + contentPaddingHorizontal(24.dp) + }, maxLines = if (seeMore) 5 else Int.MAX_VALUE, overflow = TextOverflow.Ellipsis, - modifier = HzPadding.skipToLookaheadSize(), - + modifier = Modifier.skipToLookaheadSize(), ) } val textButton = if (seeMore) { @@ -330,13 +337,15 @@ private fun Body(related: List, scroll: ScrollState) { Text( text = textButton, - style = MaterialTheme.typography.labelLarge, - textAlign = TextAlign.Center, - color = JetsnackTheme.colors.textLink, + style = { + textStyleWithFontFamilyFix(typography.labelLarge) + contentColor(colors.textLink) + textAlign(TextAlign.Center) + contentPaddingTop(15.dp) + fillWidth() + }, modifier = Modifier .heightIn(20.dp) - .fillMaxWidth() - .padding(top = 15.dp) .clickable { seeMore = !seeMore } @@ -346,16 +355,20 @@ private fun Body(related: List, scroll: ScrollState) { Spacer(Modifier.height(40.dp)) Text( text = stringResource(R.string.ingredients), - style = MaterialTheme.typography.labelSmall, - color = JetsnackTheme.colors.textHelp, - modifier = HzPadding, + style = { + textStyleWithFontFamilyFix(typography.labelSmall) + contentColor(colors.textHelp) + contentPaddingHorizontal(24.dp) + }, ) Spacer(Modifier.height(4.dp)) Text( text = stringResource(R.string.ingredients_list), - style = MaterialTheme.typography.bodyLarge, - color = JetsnackTheme.colors.textHelp, - modifier = HzPadding, + style = { + textStyleWithFontFamilyFix(typography.bodyLarge) + contentColor(colors.textHelp) + contentPaddingHorizontal(24.dp) + }, ) Spacer(Modifier.height(16.dp)) @@ -410,9 +423,11 @@ private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { Spacer(Modifier.height(16.dp)) Text( text = snack.name, - fontStyle = FontStyle.Italic, - style = MaterialTheme.typography.headlineMedium, - color = JetsnackTheme.colors.textSecondary, + style = { + textStyleWithFontFamilyFix(typography.headlineMedium) + contentColor(colors.textSecondary) + fontStyle(FontStyle.Italic) + }, modifier = HzPadding .sharedBounds( rememberSharedContentState( @@ -429,10 +444,12 @@ private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { ) Text( text = snack.tagline, - fontStyle = FontStyle.Italic, - style = MaterialTheme.typography.titleSmall, - fontSize = 20.sp, - color = JetsnackTheme.colors.textHelp, + style = { + textStyleWithFontFamilyFix(typography.titleSmall) + fontStyle(FontStyle.Italic) + contentColor(colors.textHelp) + fontSize(20.sp) + }, modifier = HzPadding .sharedBounds( rememberSharedContentState( @@ -451,8 +468,10 @@ private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { with(animatedVisibilityScope) { Text( text = formatPrice(snack.price), - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.textPrimary, + style = { + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.textPrimary) + }, modifier = HzPadding .animateEnterExit( enter = fadeIn() + slideInVertically { -it / 3 }, @@ -553,7 +572,7 @@ private fun CartBottomBar(modifier: Modifier = Modifier) { LocalNavAnimatedVisibilityScope.current ?: throw IllegalStateException("No Shared scope") with(sharedTransitionScope) { with(animatedVisibilityScope) { - JetsnackSurface( + Surface( modifier = modifier .renderInSharedTransitionScopeOverlay(zIndexInOverlay = 4f) .animateEnterExit( @@ -582,14 +601,16 @@ private fun CartBottomBar(modifier: Modifier = Modifier) { increaseItemCount = { updateCount(count + 1) }, ) Spacer(Modifier.width(16.dp)) - JetsnackButton( + Button( onClick = { /* todo */ }, modifier = Modifier.weight(1f), ) { Text( text = stringResource(R.string.add_to_cart), modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center, + style = { + textAlign(TextAlign.Center) + }, maxLines = 1, ) } diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Color.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Color.kt index eb99c9816..918312fb8 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Color.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Color.kt @@ -16,8 +16,101 @@ package com.example.jetsnack.ui.theme +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Color +/** + * Jetsnack custom Color Palette + */ +@Immutable +data class JetsnackColors( + val gradient6_1: List, + val gradient6_2: List, + val gradient3_1: List, + val gradient3_2: List, + val gradient2_1: List, + val gradient2_2: List, + val gradient2_3: List, + val brand: Color, + val brandSecondary: Color, + val uiBackground: Color, + val uiBorder: Color, + val uiFloated: Color, + val interactivePrimary: List = gradient2_1, + val interactiveSecondary: List = gradient2_2, + val interactiveMask: List = gradient6_1, + val textPrimary: Color = brand, + val textSecondary: Color, + val textHelp: Color, + val textInteractive: Color, + val textLink: Color, + val tornado1: List, + val iconPrimary: Color = brand, + val iconSecondary: Color, + val iconInteractive: Color, + val iconInteractiveInactive: Color, + val error: Color, + val notificationBadge: Color = error, + val isDark: Boolean, +) +/** + * A Material [Colors] implementation which sets all colors to [debugColor] to discourage usage of + * [MaterialTheme.colorScheme] in preference to [JetsnackTheme.colors]. + */ +fun debugColors(darkTheme: Boolean, debugColor: Color = Color.Magenta) = ColorScheme( + primary = debugColor, + onPrimary = debugColor, + primaryContainer = debugColor, + onPrimaryContainer = debugColor, + inversePrimary = debugColor, + secondary = debugColor, + onSecondary = debugColor, + secondaryContainer = debugColor, + onSecondaryContainer = debugColor, + tertiary = debugColor, + onTertiary = debugColor, + tertiaryContainer = debugColor, + onTertiaryContainer = debugColor, + background = debugColor, + onBackground = debugColor, + surface = debugColor, + onSurface = debugColor, + surfaceVariant = debugColor, + onSurfaceVariant = debugColor, + surfaceTint = debugColor, + inverseSurface = debugColor, + inverseOnSurface = debugColor, + error = debugColor, + onError = debugColor, + errorContainer = debugColor, + onErrorContainer = debugColor, + outline = debugColor, + outlineVariant = debugColor, + scrim = debugColor, + surfaceBright = debugColor, + surfaceDim = debugColor, + surfaceContainer = debugColor, + surfaceContainerHigh = debugColor, + surfaceContainerHighest = debugColor, + surfaceContainerLow = debugColor, + surfaceContainerLowest = debugColor, + primaryFixed = debugColor, + primaryFixedDim = debugColor, + onPrimaryFixed = debugColor, + onPrimaryFixedVariant = debugColor, + secondaryFixed = debugColor, + secondaryFixedDim = debugColor, + onSecondaryFixed = debugColor, + onSecondaryFixedVariant = debugColor, + tertiaryFixed = debugColor, + tertiaryFixedDim = debugColor, + onTertiaryFixed = debugColor, + onTertiaryFixedVariant = debugColor, +) + val Shadow11 = Color(0xff001787) val Shadow10 = Color(0xff00119e) val Shadow9 = Color(0xff0009b3) @@ -87,3 +180,55 @@ val FunctionalGrey = Color(0xfff6f6f6) val FunctionalDarkGrey = Color(0xff2e2e2e) const val AlphaNearOpaque = 0.95f + +internal val LightColorPalette = JetsnackColors( + brand = Shadow5, + brandSecondary = Ocean3, + uiBackground = Neutral0, + uiBorder = Neutral4, + uiFloated = FunctionalGrey, + textSecondary = Neutral7, + textHelp = Neutral6, + textInteractive = Neutral0, + textLink = Ocean11, + iconSecondary = Neutral7, + iconInteractive = Neutral0, + iconInteractiveInactive = Neutral1, + error = FunctionalRed, + gradient6_1 = listOf(Shadow4, Ocean3, Shadow2, Ocean3, Shadow4), + gradient6_2 = listOf(Rose4, Lavender3, Rose2, Lavender3, Rose4), + gradient3_1 = listOf(Shadow2, Ocean3, Shadow4), + gradient3_2 = listOf(Rose2, Lavender3, Rose4), + gradient2_1 = listOf(Shadow4, Shadow11), + gradient2_2 = listOf(Ocean3, Shadow3), + gradient2_3 = listOf(Lavender3, Rose2), + tornado1 = listOf(Shadow4, Ocean3), + isDark = false, +) + +internal val DarkColorPalette = JetsnackColors( + brand = Shadow1, + brandSecondary = Ocean2, + uiBackground = Neutral8, + uiBorder = Neutral3, + uiFloated = FunctionalDarkGrey, + textPrimary = Shadow1, + textSecondary = Neutral0, + textHelp = Neutral1, + textInteractive = Neutral7, + textLink = Ocean2, + iconPrimary = Shadow1, + iconSecondary = Neutral0, + iconInteractive = Neutral7, + iconInteractiveInactive = Neutral6, + error = FunctionalRedDark, + gradient6_1 = listOf(Shadow5, Ocean7, Shadow9, Ocean7, Shadow5), + gradient6_2 = listOf(Rose11, Lavender7, Rose8, Lavender7, Rose11), + gradient3_1 = listOf(Shadow9, Ocean7, Shadow5), + gradient3_2 = listOf(Rose8, Lavender7, Rose11), + gradient2_1 = listOf(Ocean3, Shadow3), + gradient2_2 = listOf(Ocean4, Shadow2), + gradient2_3 = listOf(Lavender3, Rose3), + tornado1 = listOf(Shadow4, Ocean3), + isDark = true, +) diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Styles.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Styles.kt new file mode 100644 index 000000000..acc31f623 --- /dev/null +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Styles.kt @@ -0,0 +1,109 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalFoundationStyleApi::class) + +package com.example.jetsnack.ui.theme + +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.disabled +import androidx.compose.foundation.style.fillWidth +import androidx.compose.foundation.style.pressed +import androidx.compose.foundation.style.selected +import androidx.compose.material3.LocalTextStyle +import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.unit.dp +import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix + +@Immutable +data class AppStyles( + val buttonStyle: Style = Style { + shape(RoundedCornerShape(percent = 50)) + background(Brush.linearGradient(colors.interactivePrimary)) + contentColor(colors.textInteractive) + contentPaddingVertical(8.dp) + contentPaddingHorizontal(24.dp) + textStyleWithFontFamilyFix(typography.labelLarge) + disabled { + animate { + background(Brush.linearGradient(colors.interactiveSecondary)) + contentColor(colors.textHelp) + } + } + }, + val cardStyle: Style = Style { + shape(shapes.medium) + background(colors.uiBackground) + contentColor(colors.textPrimary) + /* + todo elevation + elevation: Dp = 4.dp,*/ + }, + val dividerStyle: Style = Style { + background(colors.uiBorder.copy(alpha = 0.12f)) + height(1.dp) + fillWidth() + }, + val gradientIconButtonStyle: Style = Style { + shape(CircleShape) + clip(true) + border(2.dp, Brush.linearGradient(colors.interactiveSecondary)) + background(colors.uiBackground) + pressed { + animate { + background( + Brush.horizontalGradient( + // this was a parameter input into the function? might want to make helper function for it + colors = colors.interactiveSecondary, + startX = 0f, + endX = 200f, + tileMode = TileMode.Mirror, + ), + ) + } + } + }, + val filterChipStyle: Style = Style { + shape(shapes.small) + background(colors.uiBackground) + contentColor(colors.textSecondary) + border(1.dp, Brush.linearGradient(colors.interactiveSecondary)) + // todo elevation = 2.dp, + selected { + animate { + background(colors.brandSecondary) + contentColor(Color.Black) + border(1.dp, Color.Transparent) + } + } + }, + val defaultTextStyle: Style = Style { + textStyleWithFontFamilyFix(LocalTextStyle.currentValue) + }, + val surfaceStyle: Style = Style { + shape(RectangleShape) + background(colors.uiBackground) + contentColor(colors.textSecondary) + clip(true) + }, +) diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Theme.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Theme.kt index ad0a8e66c..16af20c9a 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Theme.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Theme.kt @@ -14,74 +14,71 @@ * limitations under the License. */ +@file:OptIn(ExperimentalFoundationStyleApi::class) + package com.example.jetsnack.ui.theme import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.ColorScheme +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.StyleScope import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Shapes +import androidx.compose.material3.Typography import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable +import androidx.compose.runtime.ProvidableCompositionLocal +import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.graphics.Color -private val LightColorPalette = JetsnackColors( - brand = Shadow5, - brandSecondary = Ocean3, - uiBackground = Neutral0, - uiBorder = Neutral4, - uiFloated = FunctionalGrey, - textSecondary = Neutral7, - textHelp = Neutral6, - textInteractive = Neutral0, - textLink = Ocean11, - iconSecondary = Neutral7, - iconInteractive = Neutral0, - iconInteractiveInactive = Neutral1, - error = FunctionalRed, - gradient6_1 = listOf(Shadow4, Ocean3, Shadow2, Ocean3, Shadow4), - gradient6_2 = listOf(Rose4, Lavender3, Rose2, Lavender3, Rose4), - gradient3_1 = listOf(Shadow2, Ocean3, Shadow4), - gradient3_2 = listOf(Rose2, Lavender3, Rose4), - gradient2_1 = listOf(Shadow4, Shadow11), - gradient2_2 = listOf(Ocean3, Shadow3), - gradient2_3 = listOf(Lavender3, Rose2), - tornado1 = listOf(Shadow4, Ocean3), - isDark = false, -) +@Immutable +class JetsnackTheme( + val colors: JetsnackColors = LightColorPalette, + val typography: Typography = Typography, + val shapes: Shapes = Shapes, + val appStyles: AppStyles = AppStyles(), +) { + companion object { + val colors: JetsnackColors + @Composable @ReadOnlyComposable + get() = LocalJetsnackTheme.current.colors + + val typography: Typography + @Composable @ReadOnlyComposable + get() = LocalJetsnackTheme.current.typography + + val shapes: Shapes + @Composable @ReadOnlyComposable + get() = LocalJetsnackTheme.current.shapes + + val appStyles: AppStyles + @Composable @ReadOnlyComposable + get() = LocalJetsnackTheme.current.appStyles + + val LocalJetsnackTheme: ProvidableCompositionLocal + get() = LocalJetsnackThemeInstance + } +} + +val StyleScope.colors: JetsnackColors + get() = JetsnackTheme.LocalJetsnackTheme.currentValue.colors -private val DarkColorPalette = JetsnackColors( - brand = Shadow1, - brandSecondary = Ocean2, - uiBackground = Neutral8, - uiBorder = Neutral3, - uiFloated = FunctionalDarkGrey, - textPrimary = Shadow1, - textSecondary = Neutral0, - textHelp = Neutral1, - textInteractive = Neutral7, - textLink = Ocean2, - iconPrimary = Shadow1, - iconSecondary = Neutral0, - iconInteractive = Neutral7, - iconInteractiveInactive = Neutral6, - error = FunctionalRedDark, - gradient6_1 = listOf(Shadow5, Ocean7, Shadow9, Ocean7, Shadow5), - gradient6_2 = listOf(Rose11, Lavender7, Rose8, Lavender7, Rose11), - gradient3_1 = listOf(Shadow9, Ocean7, Shadow5), - gradient3_2 = listOf(Rose8, Lavender7, Rose11), - gradient2_1 = listOf(Ocean3, Shadow3), - gradient2_2 = listOf(Ocean4, Shadow2), - gradient2_3 = listOf(Lavender3, Rose3), - tornado1 = listOf(Shadow4, Ocean3), - isDark = true, -) +val StyleScope.typography: Typography + get() = JetsnackTheme.LocalJetsnackTheme.currentValue.typography + +val StyleScope.shapes: Shapes + get() = JetsnackTheme.LocalJetsnackTheme.currentValue.shapes + +internal val LocalJetsnackThemeInstance = staticCompositionLocalOf { JetsnackTheme() } @Composable fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { val colors = if (darkTheme) DarkColorPalette else LightColorPalette + val theme = JetsnackTheme(colors = colors, appStyles = AppStyles()) - ProvideJetsnackColors(colors) { + CompositionLocalProvider( + JetsnackTheme.LocalJetsnackTheme provides theme, + ) { MaterialTheme( colorScheme = debugColors(darkTheme), typography = Typography, @@ -90,96 +87,3 @@ fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composab ) } } - -object JetsnackTheme { - val colors: JetsnackColors - @Composable - get() = LocalJetsnackColors.current -} - -/** - * Jetsnack custom Color Palette - */ -@Immutable -data class JetsnackColors( - val gradient6_1: List, - val gradient6_2: List, - val gradient3_1: List, - val gradient3_2: List, - val gradient2_1: List, - val gradient2_2: List, - val gradient2_3: List, - val brand: Color, - val brandSecondary: Color, - val uiBackground: Color, - val uiBorder: Color, - val uiFloated: Color, - val interactivePrimary: List = gradient2_1, - val interactiveSecondary: List = gradient2_2, - val interactiveMask: List = gradient6_1, - val textPrimary: Color = brand, - val textSecondary: Color, - val textHelp: Color, - val textInteractive: Color, - val textLink: Color, - val tornado1: List, - val iconPrimary: Color = brand, - val iconSecondary: Color, - val iconInteractive: Color, - val iconInteractiveInactive: Color, - val error: Color, - val notificationBadge: Color = error, - val isDark: Boolean, -) - -@Composable -fun ProvideJetsnackColors(colors: JetsnackColors, content: @Composable () -> Unit) { - CompositionLocalProvider(LocalJetsnackColors provides colors, content = content) -} - -private val LocalJetsnackColors = staticCompositionLocalOf { - error("No JetsnackColorPalette provided") -} - -/** - * A Material [Colors] implementation which sets all colors to [debugColor] to discourage usage of - * [MaterialTheme.colorScheme] in preference to [JetsnackTheme.colors]. - */ -fun debugColors(darkTheme: Boolean, debugColor: Color = Color.Magenta) = ColorScheme( - primary = debugColor, - onPrimary = debugColor, - primaryContainer = debugColor, - onPrimaryContainer = debugColor, - inversePrimary = debugColor, - secondary = debugColor, - onSecondary = debugColor, - secondaryContainer = debugColor, - onSecondaryContainer = debugColor, - tertiary = debugColor, - onTertiary = debugColor, - tertiaryContainer = debugColor, - onTertiaryContainer = debugColor, - background = debugColor, - onBackground = debugColor, - surface = debugColor, - onSurface = debugColor, - surfaceVariant = debugColor, - onSurfaceVariant = debugColor, - surfaceTint = debugColor, - inverseSurface = debugColor, - inverseOnSurface = debugColor, - error = debugColor, - onError = debugColor, - errorContainer = debugColor, - onErrorContainer = debugColor, - outline = debugColor, - outlineVariant = debugColor, - scrim = debugColor, - surfaceBright = debugColor, - surfaceDim = debugColor, - surfaceContainer = debugColor, - surfaceContainerHigh = debugColor, - surfaceContainerHighest = debugColor, - surfaceContainerLow = debugColor, - surfaceContainerLowest = debugColor, -) diff --git a/Jetsnack/gradle/libs.versions.toml b/Jetsnack/gradle/libs.versions.toml index c51b64c94..c10875b44 100644 --- a/Jetsnack/gradle/libs.versions.toml +++ b/Jetsnack/gradle/libs.versions.toml @@ -63,7 +63,7 @@ androidx-activity-ktx = { module = "androidx.activity:activity-ktx", version.ref androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-compose-animation = { module = "androidx.compose.animation:animation" } androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "androidx-compose-bom" } -androidx-compose-foundation = { module = "androidx.compose.foundation:foundation" } +androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version = "1.11.0-beta01" } androidx-compose-foundation-layout = { module = "androidx.compose.foundation:foundation-layout" } androidx-compose-material3 = { module = "androidx.compose.material3:material3" } androidx-compose-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive" }