From c7208610ae68df808949d45cb260e126c17ae46f Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 13 Mar 2026 16:28:27 +0000 Subject: [PATCH 01/33] Migrate JetSnack to use Styles --- Jetsnack/app/build.gradle.kts | 1 + .../compose/material3/CompositionLocals.kt | 41 ++++++++ .../example/jetsnack/ui/components/Button.kt | 39 ++++---- .../example/jetsnack/ui/components/Card.kt | 17 ++-- .../example/jetsnack/ui/components/Divider.kt | 18 ++-- .../example/jetsnack/ui/components/Filters.kt | 98 ++++++++++--------- .../jetsnack/ui/components/Gradient.kt | 27 +---- .../ui/components/GradientTintedIconButton.kt | 63 ++++++------ .../ui/components/QuantitySelector.kt | 3 + .../example/jetsnack/ui/components/Snacks.kt | 21 ++-- .../example/jetsnack/ui/components/Surface.kt | 37 ++++--- .../jetsnack/ui/home/DestinationBar.kt | 3 +- .../java/com/example/jetsnack/ui/home/Feed.kt | 4 +- .../java/com/example/jetsnack/ui/home/Home.kt | 9 +- .../com/example/jetsnack/ui/home/cart/Cart.kt | 4 +- .../jetsnack/ui/home/search/Results.kt | 6 +- .../example/jetsnack/ui/home/search/Search.kt | 10 +- .../com/example/jetsnack/ui/theme/Styles.kt | 91 +++++++++++++++++ .../com/example/jetsnack/ui/theme/Theme.kt | 28 ++++-- Jetsnack/gradle/libs.versions.toml | 2 +- 20 files changed, 331 insertions(+), 191 deletions(-) create mode 100644 Jetsnack/app/src/main/java/androidx/compose/material3/CompositionLocals.kt create mode 100644 Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Styles.kt diff --git a/Jetsnack/app/build.gradle.kts b/Jetsnack/app/build.gradle.kts index 0fca21037b..2d1d6a086d 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/androidx/compose/material3/CompositionLocals.kt b/Jetsnack/app/src/main/java/androidx/compose/material3/CompositionLocals.kt new file mode 100644 index 0000000000..df849fbc1c --- /dev/null +++ b/Jetsnack/app/src/main/java/androidx/compose/material3/CompositionLocals.kt @@ -0,0 +1,41 @@ +package androidx.compose.material3 + +import androidx.compose.runtime.ProvidableCompositionLocal +import java.lang.reflect.Method + +// This file is only because we don't have this yet implemented in Material 3 theming. + +// This will not be how it's done when its released :) + +val LocalShapes: ProvidableCompositionLocal + get() = getInternalLocal( + className = "androidx.compose.material3.ShapesKt", + methodName = "getLocalShapes", + readableName = "LocalShapes" + ) + +val LocalColorScheme: ProvidableCompositionLocal + get() = getInternalLocal( + className = "androidx.compose.material3.ColorSchemeKt", + methodName = "getLocalColorScheme", + readableName = "LocalColorScheme" + ) + +val LocalTypography: ProvidableCompositionLocal + get() = getInternalLocal( + className = "androidx.compose.material3.TypographyKt", + methodName = "getLocalTypography", + readableName = "LocalTypography" + ) + +private fun getInternalLocal(className: String, methodName: String, readableName: String): ProvidableCompositionLocal { + try { + val clazz = Class.forName(className) + val method: Method = clazz.getDeclaredMethod(methodName) + method.isAccessible = true + @Suppress("UNCHECKED_CAST") + return method.invoke(null) as ProvidableCompositionLocal + } catch (e: Exception) { + throw RuntimeException("Failed to access internal $readableName via reflection", e) + } +} \ No newline at end of file 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 794bd3f1d7..8ba8ac1af6 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,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 @@ -29,6 +31,10 @@ 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 @@ -47,33 +53,23 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview import com.example.jetsnack.ui.theme.JetsnackTheme +//todo think about the text style here @Composable fun JetsnackButton( 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, ) { + val styleState = rememberUpdatedStyleState(interactionSource, { + it.isEnabled = enabled + }) JetsnackSurface( - shape = shape, - color = Color.Transparent, - contentColor = if (enabled) contentColor else disabledContentColor, - border = border, + 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, @@ -91,8 +87,7 @@ fun JetsnackButton( minWidth = ButtonDefaults.MinWidth, minHeight = ButtonDefaults.MinHeight, ) - .indication(interactionSource, ripple()) - .padding(contentPadding), + .indication(interactionSource, ripple()), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, content = content, @@ -101,8 +96,6 @@ fun JetsnackButton( } } -private val ButtonShape = RoundedCornerShape(percent = 50) - @Preview("default", "round") @Preview("dark theme", "round", uiMode = UI_MODE_NIGHT_YES) @Preview("large font", "round", fontScale = 2f) @@ -122,7 +115,9 @@ private fun ButtonPreview() { private fun RectangleButtonPreview() { JetsnackTheme { JetsnackButton( - onClick = {}, shape = RectangleShape, + 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 8507472164..04d6ecd776 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,11 +14,16 @@ * 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.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.then import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -33,20 +38,12 @@ 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, + style: Style = Style, content: @Composable () -> Unit, ) { JetsnackSurface( 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 089313675a..98c4b1d475 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,35 +14,33 @@ * 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.LocalAppStyles @Composable fun JetsnackDivider( modifier: Modifier = Modifier, - color: Color = JetsnackTheme.colors.uiBorder.copy(alpha = DividerAlpha), - thickness: Dp = 1.dp, + style: Style = Style ) { - HorizontalDivider( - modifier = modifier, - color = color, - thickness = thickness, - ) + Box(modifier = modifier.styleable(null, LocalAppStyles.current.dividerStyle, style)) } -private const val DividerAlpha = 0.12f @Preview("default", showBackground = true) @Preview("dark theme", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) 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 30121ea109..ff753cbfd8 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,24 @@ 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.LocalShapes 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 +58,7 @@ 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.LocalJetsnackColors @Composable fun FilterBar( @@ -93,59 +98,62 @@ fun FilterBar( } } items(filters) { filter -> - FilterChip(filter = filter, shape = MaterialTheme.shapes.small) + FilterChip( + filter = filter, + style = Style { + shape(LocalShapes.currentValue.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, + 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 = LocalJetsnackColors.currentValue.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, 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 1a7fba80c4..58c94ef17b 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() @@ -68,16 +56,3 @@ fun Modifier.diagonalGradientBorder(colors: List, borderSize: Dp = 2.dp, 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 39ebb9070e..c65086d21e 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,25 @@ * 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.padding -import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi +import androidx.compose.foundation.style.Style +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.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember 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 +41,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 +62,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.textSecondary, + JetsnackTheme.colors.textSecondary, + ), + blendMode = blendMode, + ) + } else { + Modifier.contentTintDiagonalGradient( + colors = colors, + blendMode = blendMode, + ) + } Icon( painter = painterResource(id = iconResourceId), contentDescription = contentDescription, 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 4952bd2768..f4b21bcee8 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,6 +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.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable 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 028ee2b1ce..12dc5e31ec 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,8 +49,11 @@ 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.LocalShapes import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -189,7 +192,9 @@ 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, + style = { + shape(LocalShapes.currentValue.medium) + }, modifier = modifier.padding( start = 4.dp, end = 4.dp, @@ -280,8 +285,9 @@ private fun HighlightSnackItem( } } JetsnackCard( - elevation = 0.dp, - shape = RoundedCornerShape(roundedCornerAnimation), + style = Style { + shape( RoundedCornerShape(roundedCornerAnimation)) + }, modifier = modifier .padding(bottom = 16.dp) .sharedBounds( @@ -454,11 +460,12 @@ fun SnackImage( elevation: Dp = 0.dp, ) { JetsnackSurface( - elevation = elevation, - shape = CircleShape, + 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 1104c51f1e..1346999427 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,12 +14,19 @@ * 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.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.material3.LocalContentColor import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -43,28 +50,21 @@ import kotlin.math.ln @Composable fun JetsnackSurface( 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), + .styleable(styleState, JetsnackTheme.appStyles.surfaceStyle, style) ) { - CompositionLocalProvider(LocalContentColor provides contentColor, content = content) + //todo double check CompositionLocalProvider(LocalContentColor provides contentColor, content = content) + content() } } +/* TODO Migrate computed property @Composable private fun getBackgroundColorForElevation(color: Color, elevation: Dp): Color { return if (elevation > 0.dp // && https://issuetracker.google.com/issues/161429530 @@ -76,23 +76,28 @@ private fun getBackgroundColorForElevation(color: Color, elevation: Dp): Color { color } } +*/ /** * 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/home/DestinationBar.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/DestinationBar.kt index b706ea8bb3..7eba0922c4 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,6 +25,7 @@ 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 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 10e3125ce5..39ccd8e74b 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 @@ -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/Home.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Home.kt index 28d1f4b927..072b04001f 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 @@ -82,6 +82,7 @@ import com.example.jetsnack.ui.snackdetail.nonSpatialExpressiveSpring import com.example.jetsnack.ui.snackdetail.spatialExpressiveSpring import com.example.jetsnack.ui.theme.JetsnackTheme import java.util.Locale +import androidx.compose.ui.platform.LocalLocale fun NavGraphBuilder.composableWithCompositionLocal( route: String, @@ -174,8 +175,10 @@ fun JetsnackBottomBar( JetsnackSurface( modifier = modifier, - color = color, - contentColor = contentColor, + style = { + background(color) + contentColor(contentColor) + } ) { val springSpec = spatialExpressiveSpring() JetsnackBottomNavLayout( @@ -187,7 +190,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 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 6268546131..5afdf18663 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 @@ -449,7 +449,9 @@ private fun CheckoutBar(modifier: Modifier = Modifier) { Spacer(Modifier.weight(1f)) JetsnackButton( onClick = { /* todo */ }, - shape = RectangleShape, + style = { + shape(RectangleShape) + }, modifier = Modifier .padding(horizontal = 12.dp, vertical = 8.dp) .weight(1f), 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 05aebc129f..e2a74e017a 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 @@ -117,8 +117,10 @@ private fun SearchResult(snack: Snack, onSnackClick: (Long, String) -> Unit, sho } JetsnackButton( onClick = { /* todo */ }, - shape = CircleShape, - contentPadding = PaddingValues(0.dp), + style = { + shape(CircleShape) + contentPadding(0.dp) + }, modifier = Modifier.size(36.dp), ) { Icon( 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 33e84c51e7..85d95e31ed 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,6 +34,7 @@ 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.LocalShapes import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -61,6 +62,7 @@ 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.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.LocalJetsnackColors @Composable fun Search(onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, state: SearchState = rememberSearchState()) { @@ -170,9 +172,11 @@ private fun SearchBar( modifier: Modifier = Modifier, ) { JetsnackSurface( - color = JetsnackTheme.colors.uiFloated, - contentColor = JetsnackTheme.colors.textSecondary, - shape = MaterialTheme.shapes.small, + style = { + background(LocalJetsnackColors.currentValue.uiFloated) + contentColor(LocalJetsnackColors.currentValue.textSecondary) + shape(LocalShapes.currentValue.small) + }, modifier = modifier .fillMaxWidth() .height(56.dp) 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 0000000000..41ecc7863e --- /dev/null +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Styles.kt @@ -0,0 +1,91 @@ +@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.ButtonDefaults +import androidx.compose.material3.LocalShapes +import androidx.compose.material3.LocalTypography +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 + +@Immutable +data class AppStyles( + val buttonStyle : Style = Style { + shape( RoundedCornerShape(percent = 50)) + background(Brush.linearGradient(LocalJetsnackColors.currentValue.interactivePrimary)) + contentColor(LocalJetsnackColors.currentValue.textInteractive) + contentPadding( ButtonDefaults.ContentPadding.calculateTopPadding()) // todo file issue to support padding values + textStyle(LocalTypography.currentValue.labelLarge) + disabled { + animate { + background(Brush.linearGradient(LocalJetsnackColors.currentValue.interactiveSecondary)) + contentColor(LocalJetsnackColors.currentValue.textHelp) + } + } + }, + val cardStyle: Style = Style { + shape(LocalShapes.currentValue.medium) + background(LocalJetsnackColors.currentValue.uiBackground) + contentColor(LocalJetsnackColors.currentValue.textPrimary) + /* + todo elevation + elevation: Dp = 4.dp,*/ + }, + val dividerStyle: Style = Style { + background(LocalJetsnackColors.currentValue.uiBorder.copy(alpha = 0.12f)) + height(1.dp) + fillWidth() + }, + val gradientIconButtonStyle: Style = Style { + shape(CircleShape) + clip(true) + border(2.dp, Brush.linearGradient(LocalJetsnackColors.currentValue.interactiveSecondary)) + background(LocalJetsnackColors.currentValue.uiBackground) + pressed { + animate { + background(Brush.horizontalGradient( + // this was a parameter input into the function? might want to make helper function for it + colors = LocalJetsnackColors.currentValue.interactiveSecondary, + startX = 0f, + endX = 200f, // todo double check px/dp + tileMode = TileMode.Mirror, + )) + } + } + }, + val filterChipStyle: Style = Style { + shape(LocalShapes.currentValue.small) + background(LocalJetsnackColors.currentValue.uiBackground) + contentColor(LocalJetsnackColors.currentValue.textSecondary) + border(1.dp, Brush.linearGradient(LocalJetsnackColors.currentValue.interactiveSecondary)) + // todo elevation = 2.dp, + selected { + animate { + background(LocalJetsnackColors.currentValue.brandSecondary) + contentColor(Color.Black) + border(1.dp, Color.Transparent) + } + } + }, + val scaffoldStyle: Style = Style { + + }, + val surfaceStyle: Style = Style { + shape(RectangleShape) + background(LocalJetsnackColors.currentValue.uiBackground) + contentColor(LocalJetsnackColors.currentValue.textSecondary) + clip(true) + } +) \ No newline at end of file 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 ad0a8e66c1..2ad96a0f0a 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,14 +14,18 @@ * limitations under the License. */ +@file:OptIn(ExperimentalFoundationStyleApi::class) + package com.example.jetsnack.ui.theme import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable +import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color @@ -80,21 +84,26 @@ private val DarkColorPalette = JetsnackColors( @Composable fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { val colors = if (darkTheme) DarkColorPalette else LightColorPalette - + val styles = AppStyles() ProvideJetsnackColors(colors) { - MaterialTheme( - colorScheme = debugColors(darkTheme), - typography = Typography, - shapes = Shapes, - content = content, - ) + CompositionLocalProvider(LocalAppStyles provides styles) { + MaterialTheme( + colorScheme = debugColors(darkTheme), + typography = Typography, + shapes = Shapes, + content = content, + ) + } } } - +//todo discuss best practise for exposing composition locals with @Composable, vs ProvideCompositionLocal object JetsnackTheme { val colors: JetsnackColors @Composable get() = LocalJetsnackColors.current + val appStyles : AppStyles + @Composable + get() = LocalAppStyles.current } /** @@ -137,9 +146,10 @@ fun ProvideJetsnackColors(colors: JetsnackColors, content: @Composable () -> Uni CompositionLocalProvider(LocalJetsnackColors provides colors, content = content) } -private val LocalJetsnackColors = staticCompositionLocalOf { +val LocalJetsnackColors = staticCompositionLocalOf { error("No JetsnackColorPalette provided") } +val LocalAppStyles = staticCompositionLocalOf { AppStyles() } /** * A Material [Colors] implementation which sets all colors to [debugColor] to discourage usage of diff --git a/Jetsnack/gradle/libs.versions.toml b/Jetsnack/gradle/libs.versions.toml index c51b64c942..c10875b44e 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" } From 5a6bcb89bad29e2d5bbc82a02036755efcca185d Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 13 Mar 2026 18:18:59 +0000 Subject: [PATCH 02/33] Migrate Text() to a new JetsnackText() composable instead, to be able to use Styles API. --- .../example/jetsnack/ui/components/Button.kt | 14 +-- .../example/jetsnack/ui/components/Card.kt | 8 +- .../example/jetsnack/ui/components/Filters.kt | 9 +- .../ui/components/QuantitySelector.kt | 26 +++-- .../example/jetsnack/ui/components/Snacks.kt | 36 ++++--- .../example/jetsnack/ui/components/Text.kt | 46 ++++++++ .../jetsnack/ui/home/DestinationBar.kt | 16 +-- .../example/jetsnack/ui/home/FilterScreen.kt | 49 ++++++--- .../java/com/example/jetsnack/ui/home/Home.kt | 13 ++- .../com/example/jetsnack/ui/home/Profile.kt | 21 ++-- .../com/example/jetsnack/ui/home/cart/Cart.kt | 100 ++++++++++++------ .../jetsnack/ui/home/search/Categories.kt | 22 ++-- .../jetsnack/ui/home/search/Results.kt | 54 ++++++---- .../example/jetsnack/ui/home/search/Search.kt | 10 +- .../jetsnack/ui/home/search/Suggestions.kt | 20 ++-- .../jetsnack/ui/snackdetail/SnackDetail.kt | 98 ++++++++++------- .../com/example/jetsnack/ui/theme/Styles.kt | 15 ++- 17 files changed, 356 insertions(+), 201 deletions(-) create mode 100644 Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Text.kt 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 8ba8ac1af6..45c2622aad 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 @@ -19,18 +19,13 @@ 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 @@ -38,17 +33,12 @@ 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 @@ -103,7 +93,7 @@ fun JetsnackButton( private fun ButtonPreview() { JetsnackTheme { JetsnackButton(onClick = {}) { - Text(text = "Demo") + JetsnackText(text = "Demo") } } } @@ -119,7 +109,7 @@ private fun RectangleButtonPreview() { shape(RectangleShape) }, ) { - Text(text = "Demo") + JetsnackText(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 04d6ecd776..e8f4f2bf35 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 @@ -19,19 +19,13 @@ 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.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.foundation.style.Style import androidx.compose.foundation.style.then -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text 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 @@ -55,7 +49,7 @@ fun JetsnackCard( private fun CardPreview() { JetsnackTheme { JetsnackCard { - Text(text = "Demo", modifier = Modifier.padding(16.dp)) + JetsnackText(text = "Demo", modifier = Modifier.padding(16.dp)) } } } 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 ff753cbfd8..0c88fd7184 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 @@ -41,8 +41,7 @@ import androidx.compose.foundation.style.then import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalShapes -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import androidx.compose.material3.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -155,9 +154,11 @@ fun FilterChip( modifier = Modifier .styleable(styleState, innerBackgroundStyle), ) { - Text( + JetsnackText( text = filter.name, - style = MaterialTheme.typography.bodySmall, + style = { + jetsnackTextStyle(LocalTypography.currentValue.bodySmall) + }, maxLines = 1, modifier = Modifier.padding( horizontal = 20.dp, 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 f4b21bcee8..dc14bcdd69 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 @@ -24,8 +24,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.style.ExperimentalFoundationStyleApi -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import androidx.compose.material3.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment @@ -40,15 +39,18 @@ 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.LocalJetsnackColors @Composable fun QuantitySelector(count: Int, decreaseItemCount: () -> Unit, increaseItemCount: () -> Unit, modifier: Modifier = Modifier) { Row(modifier = modifier) { - Text( + JetsnackText( text = stringResource(R.string.quantity), - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textSecondary, - fontWeight = FontWeight.Normal, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + contentColor(LocalJetsnackColors.currentValue.textSecondary) + fontWeight(FontWeight.Normal) + }, modifier = Modifier .padding(end = 18.dp) .align(Alignment.CenterVertically), @@ -64,12 +66,14 @@ fun QuantitySelector(count: Int, decreaseItemCount: () -> Unit, increaseItemCoun modifier = Modifier .align(Alignment.CenterVertically), ) { - Text( + JetsnackText( text = "$it", - style = MaterialTheme.typography.titleSmall, - fontSize = 18.sp, - color = JetsnackTheme.colors.textPrimary, - textAlign = TextAlign.Center, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleSmall) + fontSize(18.sp) + contentColor(LocalJetsnackColors.currentValue.textPrimary) + textAlign(TextAlign.Center) + }, modifier = Modifier.widthIn(min = 24.dp), ) } 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 12dc5e31ec..cd3ee0ae8c 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 @@ -54,8 +54,7 @@ import androidx.compose.foundation.style.Style import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalShapes -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import androidx.compose.material3.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -86,6 +85,7 @@ 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.LocalJetsnackColors private val HighlightCardWidth = 170.dp private val HighlightCardPadding = 16.dp @@ -107,10 +107,12 @@ fun SnackCollection( .heightIn(min = 56.dp) .padding(start = 24.dp), ) { - Text( + JetsnackText( text = snackCollection.name, - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.brand, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleLarge) + contentColor(LocalJetsnackColors.currentValue.brand) + }, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier @@ -234,10 +236,12 @@ fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String boundsTransform = snackDetailBoundsTransform, ), ) - Text( + JetsnackText( text = snack.name, - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textSecondary, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + contentColor(LocalJetsnackColors.currentValue.textSecondary) + }, modifier = Modifier .padding(top = 8.dp) .wrapContentWidth() @@ -391,12 +395,14 @@ private fun HighlightSnackItem( } Spacer(modifier = Modifier.height(8.dp)) - Text( + JetsnackText( text = snack.name, maxLines = 1, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.textSecondary, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleLarge) + contentColor(LocalJetsnackColors.currentValue.textSecondary) + }, modifier = Modifier .padding(horizontal = 16.dp) .sharedBounds( @@ -417,10 +423,12 @@ private fun HighlightSnackItem( ) Spacer(modifier = Modifier.height(4.dp)) - Text( + JetsnackText( text = snack.tagline, - style = MaterialTheme.typography.bodyLarge, - color = JetsnackTheme.colors.textHelp, + style = { + jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + contentColor(LocalJetsnackColors.currentValue.textHelp) + }, modifier = Modifier .padding(horizontal = 16.dp) .sharedBounds( 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 0000000000..4f3a514699 --- /dev/null +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Text.kt @@ -0,0 +1,46 @@ +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.TextStyle +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.style.TextOverflow +import com.example.jetsnack.ui.theme.LocalAppStyles + +// Workaround for b/492528450 - setting textStyle currently doesn't set fontFamily. +@ExperimentalFoundationStyleApi +fun StyleScope.jetsnackTextStyle(value: TextStyle) { + textStyle(value) + value.fontFamily?.let { fontFamily(it) } +} + +@ExperimentalFoundationStyleApi +@Composable +fun JetsnackText( + 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, LocalAppStyles.current.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 7eba0922c4..f886354763 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 @@ -29,8 +29,7 @@ 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.LocalTypography import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -46,9 +45,12 @@ 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.JetsnackText +import com.example.jetsnack.ui.components.jetsnackTextStyle 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.LocalJetsnackColors @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -71,11 +73,13 @@ fun DestinationBar(modifier: Modifier = Modifier) { windowInsets = WindowInsets(0, 0, 0, 0), title = { Row { - Text( + JetsnackText( text = "Delivery to 1600 Amphitheater Way", - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textSecondary, - textAlign = TextAlign.Center, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + contentColor(LocalJetsnackColors.currentValue.textSecondary) + textAlign(TextAlign.Center) + }, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier 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 f2c0dcabc4..3b928c01a2 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 @@ -46,10 +46,10 @@ import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalTypography 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 +71,10 @@ 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.JetsnackText +import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.LocalJetsnackColors @Composable fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope, onDismiss: () -> Unit) { @@ -133,14 +136,16 @@ fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilit contentDescription = stringResource(id = R.string.close), ) } - Text( + JetsnackText( text = stringResource(id = R.string.label_filters), modifier = Modifier .fillMaxWidth() .fillMaxHeight() .padding(top = 8.dp, end = 48.dp), - textAlign = TextAlign.Center, - style = MaterialTheme.typography.titleLarge, + style = { + textAlign(TextAlign.Center) + jetsnackTextStyle(LocalTypography.currentValue.titleLarge) + } ) val resetEnabled = sortState != defaultFilter @@ -154,12 +159,16 @@ fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilit FontWeight.Normal } - Text( + JetsnackText( 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 = { + jetsnackTextStyle(LocalTypography.currentValue.bodyMedium) + fontWeight(fontWeight) + contentColor( + LocalJetsnackColors.currentValue.uiBackground + .copy(alpha = if (!resetEnabled) 0.38f else 1f) + ) + } ) } } @@ -243,10 +252,12 @@ fun SortFilters(sortFilters: List = SnackRepo.getSortFilters(), sortStat fun MaxCalories(sliderPosition: Float, onValueChanged: (Float) -> Unit) { FlowRow { FilterTitle(text = stringResource(id = R.string.max_calories)) - Text( + JetsnackText( text = stringResource(id = R.string.per_serving), - style = MaterialTheme.typography.bodyMedium, - color = JetsnackTheme.colors.brand, + style = { + jetsnackTextStyle(LocalTypography.currentValue.bodyMedium) + contentColor(LocalJetsnackColors.currentValue.brand) + }, modifier = Modifier.padding(top = 5.dp, start = 10.dp), ) } @@ -269,10 +280,12 @@ fun MaxCalories(sliderPosition: Float, onValueChanged: (Float) -> Unit) { @Composable fun FilterTitle(text: String) { - Text( + JetsnackText( text = text, - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.brand, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleLarge) + contentColor(LocalJetsnackColors.currentValue.brand) + }, modifier = Modifier.padding(bottom = 8.dp), ) } @@ -287,9 +300,11 @@ fun SortOption(text: String, @DrawableRes icon: Int?, onClickOption: () -> Unit, if (icon != null) { Icon(painter = painterResource(id = icon), contentDescription = null) } - Text( + JetsnackText( text = text, - style = MaterialTheme.typography.titleMedium, + style = { + jetsnackTextStyle(LocalTypography.currentValue.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 072b04001f..4dd136751b 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,7 @@ 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.material3.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect @@ -76,6 +75,8 @@ 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.JetsnackText +import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.home.cart.Cart import com.example.jetsnack.ui.home.search.Search import com.example.jetsnack.ui.snackdetail.nonSpatialExpressiveSpring @@ -214,10 +215,12 @@ fun JetsnackBottomBar( ) }, text = { - Text( + JetsnackText( text = text, - color = tint, - style = MaterialTheme.typography.labelLarge, + style = { + contentColor(tint) + jetsnackTextStyle(LocalTypography.currentValue.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 799fa28f3e..2c6a524cec 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,7 @@ 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.material3.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -36,6 +35,8 @@ 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.JetsnackText +import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.theme.JetsnackTheme @Composable @@ -52,17 +53,21 @@ fun Profile(modifier: Modifier = Modifier) { contentDescription = null, ) Spacer(Modifier.height(24.dp)) - Text( + JetsnackText( text = stringResource(R.string.work_in_progress), - style = MaterialTheme.typography.titleMedium, - textAlign = TextAlign.Center, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + textAlign(TextAlign.Center) + }, modifier = Modifier.fillMaxWidth(), ) Spacer(Modifier.height(16.dp)) - Text( + JetsnackText( text = stringResource(R.string.grab_beverage), - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center, + style = { + jetsnackTextStyle(LocalTypography.currentValue.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 5afdf18663..7dc5352871 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,9 @@ 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.LocalTypography 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 @@ -74,6 +74,8 @@ import com.example.jetsnack.model.SnackRepo import com.example.jetsnack.ui.components.JetsnackButton import com.example.jetsnack.ui.components.JetsnackDivider import com.example.jetsnack.ui.components.JetsnackSurface +import com.example.jetsnack.ui.components.JetsnackText +import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.components.QuantitySelector import com.example.jetsnack.ui.components.SnackCollection import com.example.jetsnack.ui.components.SnackImage @@ -82,8 +84,10 @@ 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.LocalJetsnackColors import com.example.jetsnack.ui.utils.formatPrice import kotlin.math.roundToInt +import androidx.compose.ui.platform.LocalResources @Composable fun Cart( @@ -141,7 +145,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, @@ -157,10 +161,12 @@ private fun CartContent( WindowInsets.statusBars.add(WindowInsets(top = 56.dp)), ), ) - Text( + JetsnackText( text = stringResource(R.string.cart_order_header, snackCountFormattedString), - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.brand, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleLarge) + contentColor(LocalJetsnackColors.currentValue.brand) + }, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier @@ -269,11 +275,13 @@ private fun SwipeDismissItemBackground(progress: Float) { if (progress > 0.5f) 1f else 0.5f, label = "text alpha", ) if (progress > 0.5f) { - Text( + JetsnackText( text = stringResource(id = R.string.remove_item), - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.uiBackground, - textAlign = TextAlign.Center, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + contentColor(LocalJetsnackColors.currentValue.uiBackground) + textAlign(TextAlign.Center) + }, modifier = Modifier .graphicsLayer( alpha = textAlpha, @@ -319,10 +327,12 @@ fun CartItem( .padding(start = 16.dp), ) { Row(modifier = Modifier.fillMaxWidth()) { - Text( + JetsnackText( text = snack.name, - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textSecondary, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + contentColor(LocalJetsnackColors.currentValue.textSecondary) + }, modifier = Modifier .weight(1f) .padding(top = 16.dp, end = 16.dp), @@ -338,20 +348,24 @@ fun CartItem( ) } } - Text( + JetsnackText( text = snack.tagline, - style = MaterialTheme.typography.bodyLarge, - color = JetsnackTheme.colors.textHelp, + style = { + jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + contentColor(LocalJetsnackColors.currentValue.textHelp) + }, modifier = Modifier.padding(end = 16.dp), ) Spacer(Modifier.height(8.dp)) Row( modifier = Modifier.fillMaxWidth(), ) { - Text( + JetsnackText( text = formatPrice(snack.price), - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textPrimary, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + contentColor(LocalJetsnackColors.currentValue.textPrimary) + }, modifier = Modifier .weight(1f) .padding(end = 16.dp) @@ -373,10 +387,12 @@ fun CartItem( @Composable fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifier) { Column(modifier) { - Text( + JetsnackText( text = stringResource(R.string.cart_summary_header), - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.brand, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleLarge) + contentColor(LocalJetsnackColors.currentValue.brand) + }, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier @@ -385,50 +401,62 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi .wrapContentHeight(), ) Row(modifier = Modifier.padding(horizontal = 24.dp)) { - Text( + JetsnackText( text = stringResource(R.string.cart_subtotal_label), - style = MaterialTheme.typography.bodyLarge, + style = { + jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + }, modifier = Modifier .weight(1f) .wrapContentWidth(Alignment.Start) .alignBy(LastBaseline), ) - Text( + JetsnackText( text = formatPrice(subtotal), - style = MaterialTheme.typography.bodyLarge, + style = { + jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + }, modifier = Modifier.alignBy(LastBaseline), ) } Row(modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) { - Text( + JetsnackText( text = stringResource(R.string.cart_shipping_label), - style = MaterialTheme.typography.bodyLarge, + style = { + jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + }, modifier = Modifier .weight(1f) .wrapContentWidth(Alignment.Start) .alignBy(LastBaseline), ) - Text( + JetsnackText( text = formatPrice(shippingCosts), - style = MaterialTheme.typography.bodyLarge, + style = { + jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + }, modifier = Modifier.alignBy(LastBaseline), ) } Spacer(modifier = Modifier.height(8.dp)) JetsnackDivider() Row(modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) { - Text( + JetsnackText( text = stringResource(R.string.cart_total_label), - style = MaterialTheme.typography.bodyLarge, + style = { + jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + }, modifier = Modifier .weight(1f) .padding(end = 16.dp) .wrapContentWidth(Alignment.End) .alignBy(LastBaseline), ) - Text( + JetsnackText( text = formatPrice(subtotal + shippingCosts), - style = MaterialTheme.typography.titleMedium, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + }, modifier = Modifier.alignBy(LastBaseline), ) } @@ -456,10 +484,12 @@ private fun CheckoutBar(modifier: Modifier = Modifier) { .padding(horizontal = 12.dp, vertical = 8.dp) .weight(1f), ) { - Text( + JetsnackText( 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 dba32a0023..47dce9c495 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,7 @@ 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.material3.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -45,9 +44,12 @@ 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.JetsnackText +import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.components.SnackImage import com.example.jetsnack.ui.components.VerticalGrid import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.LocalJetsnackColors import kotlin.math.max @Composable @@ -63,10 +65,12 @@ fun SearchCategories(categories: List) { @Composable private fun SearchCategoryCollection(collection: SearchCategoryCollection, index: Int, modifier: Modifier = Modifier) { Column(modifier) { - Text( + JetsnackText( text = collection.name, - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.textPrimary, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleLarge) + contentColor(LocalJetsnackColors.currentValue.textPrimary) + }, modifier = Modifier .heightIn(min = 56.dp) .padding(horizontal = 24.dp, vertical = 4.dp) @@ -103,10 +107,12 @@ private fun SearchCategory(category: SearchCategory, gradient: List, modi .background(Brush.horizontalGradient(gradient)) .clickable { /* todo */ }, content = { - Text( + JetsnackText( text = category.name, - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textSecondary, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + contentColor(LocalJetsnackColors.currentValue.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 e2a74e017a..e802d9ec34 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 @@ -34,8 +34,7 @@ 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.material3.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -50,17 +49,22 @@ import com.example.jetsnack.model.snacks import com.example.jetsnack.ui.components.JetsnackButton import com.example.jetsnack.ui.components.JetsnackDivider import com.example.jetsnack.ui.components.JetsnackSurface +import com.example.jetsnack.ui.components.JetsnackText +import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.components.SnackImage import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.LocalJetsnackColors import com.example.jetsnack.ui.utils.formatPrice @Composable fun SearchResults(searchResults: List, onSnackClick: (Long, String) -> Unit) { Column { - Text( + JetsnackText( text = stringResource(R.string.search_count, searchResults.size), - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.textPrimary, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleLarge) + contentColor(LocalJetsnackColors.currentValue.textPrimary) + }, modifier = Modifier.padding(horizontal = 24.dp, vertical = 4.dp), ) LazyColumn { @@ -98,21 +102,27 @@ private fun SearchResult(snack: Snack, onSnackClick: (Long, String) -> Unit, sho .weight(1f) .padding(start = 16.dp, end = 16.dp), ) { - Text( + JetsnackText( text = snack.name, - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textSecondary, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + contentColor(LocalJetsnackColors.currentValue.textSecondary) + }, ) - Text( + JetsnackText( text = snack.tagline, - style = MaterialTheme.typography.bodyLarge, - color = JetsnackTheme.colors.textHelp, + style = { + jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + contentColor(LocalJetsnackColors.currentValue.textHelp) + }, ) Spacer(Modifier.height(8.dp)) - Text( + JetsnackText( text = formatPrice(snack.price), - style = MaterialTheme.typography.titleMedium, - color = JetsnackTheme.colors.textPrimary, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + contentColor(LocalJetsnackColors.currentValue.textPrimary) + }, ) } JetsnackButton( @@ -146,17 +156,21 @@ fun NoResults(query: String, modifier: Modifier = Modifier) { contentDescription = null, ) Spacer(Modifier.height(24.dp)) - Text( + JetsnackText( text = stringResource(R.string.search_no_matches, query), - style = MaterialTheme.typography.titleMedium, - textAlign = TextAlign.Center, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + textAlign(TextAlign.Center) + }, modifier = Modifier.fillMaxWidth(), ) Spacer(Modifier.height(16.dp)) - Text( + JetsnackText( text = stringResource(R.string.search_no_matches_retry), - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center, + style = { + jetsnackTextStyle(LocalTypography.currentValue.bodyMedium) + textAlign(TextAlign.Center) + }, modifier = Modifier.fillMaxWidth(), ) } 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 85d95e31ed..823322656c 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 @@ -35,8 +35,7 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalShapes -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import androidx.compose.material3.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable @@ -61,6 +60,7 @@ 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.JetsnackText import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.theme.LocalJetsnackColors @@ -241,9 +241,11 @@ private fun SearchHint() { contentDescription = stringResource(R.string.label_search), ) Spacer(Modifier.width(8.dp)) - Text( + JetsnackText( text = stringResource(R.string.search_jetsnack), - color = JetsnackTheme.colors.textHelp, + style = { + contentColor(LocalJetsnackColors.currentValue.textHelp) + } ) } } 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 62b9ff40c1..306a2f29a7 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,7 @@ 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.material3.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -36,7 +35,10 @@ 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.JetsnackText +import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.LocalJetsnackColors @Composable fun SearchSuggestions(suggestions: List, onSuggestionSelect: (String) -> Unit) { @@ -61,10 +63,12 @@ fun SearchSuggestions(suggestions: List, onSuggestionSele @Composable private fun SuggestionHeader(name: String, modifier: Modifier = Modifier) { - Text( + JetsnackText( text = name, - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.textPrimary, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleLarge) + contentColor(LocalJetsnackColors.currentValue.textPrimary) + }, modifier = modifier .heightIn(min = 56.dp) .padding(horizontal = 24.dp, vertical = 4.dp) @@ -74,9 +78,11 @@ private fun SuggestionHeader(name: String, modifier: Modifier = Modifier) { @Composable private fun Suggestion(suggestion: String, onSuggestionSelect: (String) -> Unit, modifier: Modifier = Modifier) { - Text( + JetsnackText( text = suggestion, - style = MaterialTheme.typography.titleMedium, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + }, modifier = modifier .heightIn(min = 48.dp) .clickable { onSuggestionSelect(suggestion) } 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 32cf6dd0c5..55d33fadec 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,12 @@ 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.LocalTypography 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 @@ -107,10 +108,13 @@ import com.example.jetsnack.ui.components.JetsnackButton 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.JetsnackText +import com.example.jetsnack.ui.components.jetsnackTextStyle 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.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.LocalJetsnackColors import com.example.jetsnack.ui.theme.Neutral8 import com.example.jetsnack.ui.utils.formatPrice import kotlin.math.max @@ -303,23 +307,27 @@ private fun Body(related: List, scroll: ScrollState) { ) { Column { Spacer(Modifier.height(TitleHeight)) - Text( + JetsnackText( text = stringResource(R.string.detail_header), - style = MaterialTheme.typography.labelSmall, - color = JetsnackTheme.colors.textHelp, - modifier = HzPadding, + style = { + jetsnackTextStyle(LocalTypography.currentValue.headlineMedium) + contentColor(LocalJetsnackColors.currentValue.textHelp) + contentPaddingHorizontal(24.dp) + } ) Spacer(Modifier.height(16.dp)) var seeMore by remember { mutableStateOf(true) } with(sharedTransitionScope) { - Text( + JetsnackText( text = stringResource(R.string.detail_placeholder), - style = MaterialTheme.typography.bodyLarge, - color = JetsnackTheme.colors.textHelp, + style = { + jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + contentColor(LocalJetsnackColors.currentValue.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) { @@ -328,15 +336,17 @@ private fun Body(related: List, scroll: ScrollState) { stringResource(id = R.string.see_less) } - Text( + JetsnackText( text = textButton, - style = MaterialTheme.typography.labelLarge, - textAlign = TextAlign.Center, - color = JetsnackTheme.colors.textLink, + style = { + jetsnackTextStyle(LocalTypography.currentValue.labelLarge) + contentColor(LocalJetsnackColors.currentValue.textLink) + textAlign(TextAlign.Center) + contentPaddingTop(15.dp) + fillWidth() + }, modifier = Modifier .heightIn(20.dp) - .fillMaxWidth() - .padding(top = 15.dp) .clickable { seeMore = !seeMore } @@ -344,18 +354,22 @@ private fun Body(related: List, scroll: ScrollState) { ) Spacer(Modifier.height(40.dp)) - Text( + JetsnackText( text = stringResource(R.string.ingredients), - style = MaterialTheme.typography.labelSmall, - color = JetsnackTheme.colors.textHelp, - modifier = HzPadding, + style = { + jetsnackTextStyle(LocalTypography.currentValue.labelSmall) + contentColor(LocalJetsnackColors.currentValue.textHelp) + contentPaddingHorizontal(24.dp) + } ) Spacer(Modifier.height(4.dp)) - Text( + JetsnackText( text = stringResource(R.string.ingredients_list), - style = MaterialTheme.typography.bodyLarge, - color = JetsnackTheme.colors.textHelp, - modifier = HzPadding, + style = { + jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + contentColor(LocalJetsnackColors.currentValue.textHelp) + contentPaddingHorizontal(24.dp) + } ) Spacer(Modifier.height(16.dp)) @@ -408,11 +422,13 @@ private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { .background(JetsnackTheme.colors.uiBackground), ) { Spacer(Modifier.height(16.dp)) - Text( + JetsnackText( text = snack.name, - fontStyle = FontStyle.Italic, - style = MaterialTheme.typography.headlineMedium, - color = JetsnackTheme.colors.textSecondary, + style = { + jetsnackTextStyle(LocalTypography.currentValue.headlineMedium) + contentColor(LocalJetsnackColors.currentValue.textSecondary) + fontStyle(FontStyle.Italic) + }, modifier = HzPadding .sharedBounds( rememberSharedContentState( @@ -427,12 +443,14 @@ private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { ) .wrapContentWidth(), ) - Text( + JetsnackText( text = snack.tagline, - fontStyle = FontStyle.Italic, - style = MaterialTheme.typography.titleSmall, - fontSize = 20.sp, - color = JetsnackTheme.colors.textHelp, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleSmall) + fontStyle(FontStyle.Italic) + contentColor(LocalJetsnackColors.currentValue.textHelp) + fontSize(20.sp) + }, modifier = HzPadding .sharedBounds( rememberSharedContentState( @@ -449,10 +467,12 @@ private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { ) Spacer(Modifier.height(4.dp)) with(animatedVisibilityScope) { - Text( + JetsnackText( text = formatPrice(snack.price), - style = MaterialTheme.typography.titleLarge, - color = JetsnackTheme.colors.textPrimary, + style = { + jetsnackTextStyle(LocalTypography.currentValue.titleLarge) + contentColor(LocalJetsnackColors.currentValue.textPrimary) + }, modifier = HzPadding .animateEnterExit( enter = fadeIn() + slideInVertically { -it / 3 }, @@ -586,10 +606,12 @@ private fun CartBottomBar(modifier: Modifier = Modifier) { onClick = { /* todo */ }, modifier = Modifier.weight(1f), ) { - Text( + JetsnackText( 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/Styles.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Styles.kt index 41ecc7863e..741ce488b9 100644 --- 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 @@ -8,17 +8,22 @@ 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.hovered import androidx.compose.foundation.style.pressed import androidx.compose.foundation.style.selected import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.LocalShapes +import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.LocalTypography +import androidx.compose.material3.Typography import androidx.compose.runtime.Immutable +import androidx.compose.runtime.ProvidableCompositionLocal 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.jetsnackTextStyle @Immutable data class AppStyles( @@ -27,7 +32,7 @@ data class AppStyles( background(Brush.linearGradient(LocalJetsnackColors.currentValue.interactivePrimary)) contentColor(LocalJetsnackColors.currentValue.textInteractive) contentPadding( ButtonDefaults.ContentPadding.calculateTopPadding()) // todo file issue to support padding values - textStyle(LocalTypography.currentValue.labelLarge) + jetsnackTextStyle(LocalTypography.currentValue.labelLarge) disabled { animate { background(Brush.linearGradient(LocalJetsnackColors.currentValue.interactiveSecondary)) @@ -59,7 +64,7 @@ data class AppStyles( // this was a parameter input into the function? might want to make helper function for it colors = LocalJetsnackColors.currentValue.interactiveSecondary, startX = 0f, - endX = 200f, // todo double check px/dp + endX = 200f, tileMode = TileMode.Mirror, )) } @@ -79,8 +84,8 @@ data class AppStyles( } } }, - val scaffoldStyle: Style = Style { - + val defaultTextStyle: Style = Style { + jetsnackTextStyle(LocalTextStyle.currentValue) }, val surfaceStyle: Style = Style { shape(RectangleShape) @@ -88,4 +93,4 @@ data class AppStyles( contentColor(LocalJetsnackColors.currentValue.textSecondary) clip(true) } -) \ No newline at end of file +) From f89e3801258dccf2e2559f3a5aeb21b945ba5af8 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 16 Mar 2026 12:04:43 +0000 Subject: [PATCH 03/33] Refactor Jetsnack theme to use a unified `JetsnackTheme` class and one `CompositionLocal`. - Deleted `CompositionLocals.kt` which used reflection to access internal Material 3 locals. - Introduced `JetsnackTheme` class to encapsulate `JetsnackColors`, `Typography`, `Shapes`, and `AppStyles`. - Replaced individual `LocalJetsnackColors`, `LocalAppStyles`, and Material 3 `LocalTypography`/`LocalShapes` lookups with `JetsnackTheme.colors`, `JetsnackTheme.typography`, etc. - Added a `StyleScope.currentJetsnackTheme` helper to provide easy access to the theme within `Style` blocks. - Moved `JetsnackColors` and color palette definitions to `Color.kt`. - Updated `debugColors` to include newer Material 3 color roles. --- .../compose/material3/CompositionLocals.kt | 41 ---- .../example/jetsnack/ui/components/Divider.kt | 4 +- .../example/jetsnack/ui/components/Filters.kt | 10 +- .../ui/components/QuantitySelector.kt | 11 +- .../example/jetsnack/ui/components/Snacks.kt | 22 +- .../example/jetsnack/ui/components/Text.kt | 4 +- .../jetsnack/ui/home/DestinationBar.kt | 7 +- .../example/jetsnack/ui/home/FilterScreen.kt | 19 +- .../java/com/example/jetsnack/ui/home/Home.kt | 4 +- .../com/example/jetsnack/ui/home/Profile.kt | 6 +- .../com/example/jetsnack/ui/home/cart/Cart.kt | 39 ++-- .../jetsnack/ui/home/search/Categories.kt | 11 +- .../jetsnack/ui/home/search/Results.kt | 23 +- .../example/jetsnack/ui/home/search/Search.kt | 12 +- .../jetsnack/ui/home/search/Suggestions.kt | 9 +- .../jetsnack/ui/snackdetail/SnackDetail.kt | 35 ++-- .../com/example/jetsnack/ui/theme/Color.kt | 146 +++++++++++++ .../com/example/jetsnack/ui/theme/Styles.kt | 47 ++--- .../com/example/jetsnack/ui/theme/Theme.kt | 198 ++++-------------- 19 files changed, 311 insertions(+), 337 deletions(-) delete mode 100644 Jetsnack/app/src/main/java/androidx/compose/material3/CompositionLocals.kt diff --git a/Jetsnack/app/src/main/java/androidx/compose/material3/CompositionLocals.kt b/Jetsnack/app/src/main/java/androidx/compose/material3/CompositionLocals.kt deleted file mode 100644 index df849fbc1c..0000000000 --- a/Jetsnack/app/src/main/java/androidx/compose/material3/CompositionLocals.kt +++ /dev/null @@ -1,41 +0,0 @@ -package androidx.compose.material3 - -import androidx.compose.runtime.ProvidableCompositionLocal -import java.lang.reflect.Method - -// This file is only because we don't have this yet implemented in Material 3 theming. - -// This will not be how it's done when its released :) - -val LocalShapes: ProvidableCompositionLocal - get() = getInternalLocal( - className = "androidx.compose.material3.ShapesKt", - methodName = "getLocalShapes", - readableName = "LocalShapes" - ) - -val LocalColorScheme: ProvidableCompositionLocal - get() = getInternalLocal( - className = "androidx.compose.material3.ColorSchemeKt", - methodName = "getLocalColorScheme", - readableName = "LocalColorScheme" - ) - -val LocalTypography: ProvidableCompositionLocal - get() = getInternalLocal( - className = "androidx.compose.material3.TypographyKt", - methodName = "getLocalTypography", - readableName = "LocalTypography" - ) - -private fun getInternalLocal(className: String, methodName: String, readableName: String): ProvidableCompositionLocal { - try { - val clazz = Class.forName(className) - val method: Method = clazz.getDeclaredMethod(methodName) - method.isAccessible = true - @Suppress("UNCHECKED_CAST") - return method.invoke(null) as ProvidableCompositionLocal - } catch (e: Exception) { - throw RuntimeException("Failed to access internal $readableName via reflection", e) - } -} \ No newline at end of file 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 98c4b1d475..88b35269a4 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 @@ -31,14 +31,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.theme.JetsnackTheme -import com.example.jetsnack.ui.theme.LocalAppStyles +import com.example.jetsnack.ui.theme.JetsnackTheme.Companion.LocalJetsnackTheme @Composable fun JetsnackDivider( modifier: Modifier = Modifier, style: Style = Style ) { - Box(modifier = modifier.styleable(null, LocalAppStyles.current.dividerStyle, style)) + Box(modifier = modifier.styleable(null, LocalJetsnackTheme.current.appStyles.dividerStyle, style)) } 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 0c88fd7184..92b11cbedd 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 @@ -40,8 +40,6 @@ 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.LocalShapes -import androidx.compose.material3.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -57,7 +55,7 @@ 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.LocalJetsnackColors +import com.example.jetsnack.ui.theme.currentJetsnackTheme @Composable fun FilterBar( @@ -100,7 +98,7 @@ fun FilterBar( FilterChip( filter = filter, style = Style { - shape(LocalShapes.currentValue.small) + shape(currentJetsnackTheme.shapes.small) }, ) } @@ -141,7 +139,7 @@ fun FilterChip( animate { background( Brush.horizontalGradient( - colors = LocalJetsnackColors.currentValue.interactiveSecondary, + colors = currentJetsnackTheme.colors.interactiveSecondary, startX = 0f, endX = 200f, tileMode = TileMode.Mirror, @@ -157,7 +155,7 @@ fun FilterChip( JetsnackText( text = filter.name, style = { - jetsnackTextStyle(LocalTypography.currentValue.bodySmall) + jetsnackTextStyle(currentJetsnackTheme.typography.bodySmall) }, maxLines = 1, modifier = Modifier.padding( 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 dc14bcdd69..a8e15c37cc 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 @@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.style.ExperimentalFoundationStyleApi -import androidx.compose.material3.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment @@ -39,7 +38,7 @@ 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.LocalJetsnackColors +import com.example.jetsnack.ui.theme.currentJetsnackTheme @Composable fun QuantitySelector(count: Int, decreaseItemCount: () -> Unit, increaseItemCount: () -> Unit, modifier: Modifier = Modifier) { @@ -47,8 +46,8 @@ fun QuantitySelector(count: Int, decreaseItemCount: () -> Unit, increaseItemCoun JetsnackText( text = stringResource(R.string.quantity), style = { - jetsnackTextStyle(LocalTypography.currentValue.titleMedium) - contentColor(LocalJetsnackColors.currentValue.textSecondary) + jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) + contentColor(currentJetsnackTheme.colors.textSecondary) fontWeight(FontWeight.Normal) }, modifier = Modifier @@ -69,9 +68,9 @@ fun QuantitySelector(count: Int, decreaseItemCount: () -> Unit, increaseItemCoun JetsnackText( text = "$it", style = { - jetsnackTextStyle(LocalTypography.currentValue.titleSmall) + jetsnackTextStyle(currentJetsnackTheme.typography.titleSmall) fontSize(18.sp) - contentColor(LocalJetsnackColors.currentValue.textPrimary) + contentColor(currentJetsnackTheme.colors.textPrimary) textAlign(TextAlign.Center) }, modifier = Modifier.widthIn(min = 24.dp), 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 cd3ee0ae8c..b0423c0fab 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 @@ -53,8 +53,6 @@ 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.LocalShapes -import androidx.compose.material3.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -85,7 +83,7 @@ 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.LocalJetsnackColors +import com.example.jetsnack.ui.theme.currentJetsnackTheme private val HighlightCardWidth = 170.dp private val HighlightCardPadding = 16.dp @@ -110,8 +108,8 @@ fun SnackCollection( JetsnackText( text = snackCollection.name, style = { - jetsnackTextStyle(LocalTypography.currentValue.titleLarge) - contentColor(LocalJetsnackColors.currentValue.brand) + jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) + contentColor(currentJetsnackTheme.colors.brand) }, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -195,7 +193,7 @@ private fun Snacks(snackCollectionId: Long, snacks: List, onSnackClick: ( fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier) { JetsnackSurface( style = { - shape(LocalShapes.currentValue.medium) + shape(currentJetsnackTheme.shapes.medium) }, modifier = modifier.padding( start = 4.dp, @@ -239,8 +237,8 @@ fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String JetsnackText( text = snack.name, style = { - jetsnackTextStyle(LocalTypography.currentValue.titleMedium) - contentColor(LocalJetsnackColors.currentValue.textSecondary) + jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) + contentColor(currentJetsnackTheme.colors.textSecondary) }, modifier = Modifier .padding(top = 8.dp) @@ -400,8 +398,8 @@ private fun HighlightSnackItem( maxLines = 1, overflow = TextOverflow.Ellipsis, style = { - jetsnackTextStyle(LocalTypography.currentValue.titleLarge) - contentColor(LocalJetsnackColors.currentValue.textSecondary) + jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) + contentColor(currentJetsnackTheme.colors.textSecondary) }, modifier = Modifier .padding(horizontal = 16.dp) @@ -426,8 +424,8 @@ private fun HighlightSnackItem( JetsnackText( text = snack.tagline, style = { - jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) - contentColor(LocalJetsnackColors.currentValue.textHelp) + jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) + contentColor(currentJetsnackTheme.colors.textHelp) }, modifier = Modifier .padding(horizontal = 16.dp) 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 index 4f3a514699..2b3a9af21b 100644 --- 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 @@ -11,7 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.style.TextOverflow -import com.example.jetsnack.ui.theme.LocalAppStyles +import com.example.jetsnack.ui.theme.JetsnackTheme // Workaround for b/492528450 - setting textStyle currently doesn't set fontFamily. @ExperimentalFoundationStyleApi @@ -35,7 +35,7 @@ fun JetsnackText( ) { BasicText( text = text, - modifier = modifier.styleable(null, LocalAppStyles.current.defaultTextStyle, style), + modifier = modifier.styleable(null, JetsnackTheme.appStyles.defaultTextStyle, style), onTextLayout = onTextLayout, overflow = overflow, softWrap = softWrap, 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 f886354763..a986b7743a 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 @@ -29,7 +29,6 @@ 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.LocalTypography import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -50,7 +49,7 @@ import com.example.jetsnack.ui.components.jetsnackTextStyle 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.LocalJetsnackColors +import com.example.jetsnack.ui.theme.currentJetsnackTheme @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -76,8 +75,8 @@ fun DestinationBar(modifier: Modifier = Modifier) { JetsnackText( text = "Delivery to 1600 Amphitheater Way", style = { - jetsnackTextStyle(LocalTypography.currentValue.titleMedium) - contentColor(LocalJetsnackColors.currentValue.textSecondary) + jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) + contentColor(currentJetsnackTheme.colors.textSecondary) textAlign(TextAlign.Center) }, maxLines = 1, 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 3b928c01a2..7bd9e102d1 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 @@ -46,7 +46,6 @@ import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.LocalTypography import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults @@ -74,7 +73,7 @@ import com.example.jetsnack.ui.components.FilterChip import com.example.jetsnack.ui.components.JetsnackText import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.theme.JetsnackTheme -import com.example.jetsnack.ui.theme.LocalJetsnackColors +import com.example.jetsnack.ui.theme.currentJetsnackTheme @Composable fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope, onDismiss: () -> Unit) { @@ -144,7 +143,7 @@ fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilit .padding(top = 8.dp, end = 48.dp), style = { textAlign(TextAlign.Center) - jetsnackTextStyle(LocalTypography.currentValue.titleLarge) + jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) } ) val resetEnabled = sortState != defaultFilter @@ -162,10 +161,10 @@ fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilit JetsnackText( text = stringResource(id = R.string.reset), style = { - jetsnackTextStyle(LocalTypography.currentValue.bodyMedium) + jetsnackTextStyle(currentJetsnackTheme.typography.bodyMedium) fontWeight(fontWeight) contentColor( - LocalJetsnackColors.currentValue.uiBackground + currentJetsnackTheme.colors.uiBackground .copy(alpha = if (!resetEnabled) 0.38f else 1f) ) } @@ -255,8 +254,8 @@ fun MaxCalories(sliderPosition: Float, onValueChanged: (Float) -> Unit) { JetsnackText( text = stringResource(id = R.string.per_serving), style = { - jetsnackTextStyle(LocalTypography.currentValue.bodyMedium) - contentColor(LocalJetsnackColors.currentValue.brand) + jetsnackTextStyle(currentJetsnackTheme.typography.bodyMedium) + contentColor(currentJetsnackTheme.colors.brand) }, modifier = Modifier.padding(top = 5.dp, start = 10.dp), ) @@ -283,8 +282,8 @@ fun FilterTitle(text: String) { JetsnackText( text = text, style = { - jetsnackTextStyle(LocalTypography.currentValue.titleLarge) - contentColor(LocalJetsnackColors.currentValue.brand) + jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) + contentColor(currentJetsnackTheme.colors.brand) }, modifier = Modifier.padding(bottom = 8.dp), ) @@ -303,7 +302,7 @@ fun SortOption(text: String, @DrawableRes icon: Int?, onClickOption: () -> Unit, JetsnackText( text = text, style = { - jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) }, modifier = Modifier .padding(start = 10.dp) 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 4dd136751b..ada5b2abd0 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,7 +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.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect @@ -82,6 +81,7 @@ 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.currentJetsnackTheme import java.util.Locale import androidx.compose.ui.platform.LocalLocale @@ -219,7 +219,7 @@ fun JetsnackBottomBar( text = text, style = { contentColor(tint) - jetsnackTextStyle(LocalTypography.currentValue.labelLarge) + jetsnackTextStyle(currentJetsnackTheme.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 2c6a524cec..8510712f33 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,7 +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.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -38,6 +37,7 @@ import com.example.jetsnack.R import com.example.jetsnack.ui.components.JetsnackText import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.currentJetsnackTheme @Composable fun Profile(modifier: Modifier = Modifier) { @@ -56,7 +56,7 @@ fun Profile(modifier: Modifier = Modifier) { JetsnackText( text = stringResource(R.string.work_in_progress), style = { - jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) textAlign(TextAlign.Center) }, modifier = Modifier.fillMaxWidth(), @@ -65,7 +65,7 @@ fun Profile(modifier: Modifier = Modifier) { JetsnackText( text = stringResource(R.string.grab_beverage), style = { - jetsnackTextStyle(LocalTypography.currentValue.bodyMedium) + jetsnackTextStyle(currentJetsnackTheme.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 7dc5352871..a638c1b790 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,7 +45,6 @@ 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.LocalTypography import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable @@ -84,7 +83,7 @@ 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.LocalJetsnackColors +import com.example.jetsnack.ui.theme.currentJetsnackTheme import com.example.jetsnack.ui.utils.formatPrice import kotlin.math.roundToInt import androidx.compose.ui.platform.LocalResources @@ -164,8 +163,8 @@ private fun CartContent( JetsnackText( text = stringResource(R.string.cart_order_header, snackCountFormattedString), style = { - jetsnackTextStyle(LocalTypography.currentValue.titleLarge) - contentColor(LocalJetsnackColors.currentValue.brand) + jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) + contentColor(currentJetsnackTheme.colors.brand) }, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -278,8 +277,8 @@ private fun SwipeDismissItemBackground(progress: Float) { JetsnackText( text = stringResource(id = R.string.remove_item), style = { - jetsnackTextStyle(LocalTypography.currentValue.titleMedium) - contentColor(LocalJetsnackColors.currentValue.uiBackground) + jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) + contentColor(currentJetsnackTheme.colors.uiBackground) textAlign(TextAlign.Center) }, modifier = Modifier @@ -330,8 +329,8 @@ fun CartItem( JetsnackText( text = snack.name, style = { - jetsnackTextStyle(LocalTypography.currentValue.titleMedium) - contentColor(LocalJetsnackColors.currentValue.textSecondary) + jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) + contentColor(currentJetsnackTheme.colors.textSecondary) }, modifier = Modifier .weight(1f) @@ -351,8 +350,8 @@ fun CartItem( JetsnackText( text = snack.tagline, style = { - jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) - contentColor(LocalJetsnackColors.currentValue.textHelp) + jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) + contentColor(currentJetsnackTheme.colors.textHelp) }, modifier = Modifier.padding(end = 16.dp), ) @@ -363,8 +362,8 @@ fun CartItem( JetsnackText( text = formatPrice(snack.price), style = { - jetsnackTextStyle(LocalTypography.currentValue.titleMedium) - contentColor(LocalJetsnackColors.currentValue.textPrimary) + jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) + contentColor(currentJetsnackTheme.colors.textPrimary) }, modifier = Modifier .weight(1f) @@ -390,8 +389,8 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi JetsnackText( text = stringResource(R.string.cart_summary_header), style = { - jetsnackTextStyle(LocalTypography.currentValue.titleLarge) - contentColor(LocalJetsnackColors.currentValue.brand) + jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) + contentColor(currentJetsnackTheme.colors.brand) }, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -404,7 +403,7 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi JetsnackText( text = stringResource(R.string.cart_subtotal_label), style = { - jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) }, modifier = Modifier .weight(1f) @@ -414,7 +413,7 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi JetsnackText( text = formatPrice(subtotal), style = { - jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) }, modifier = Modifier.alignBy(LastBaseline), ) @@ -423,7 +422,7 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi JetsnackText( text = stringResource(R.string.cart_shipping_label), style = { - jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) }, modifier = Modifier .weight(1f) @@ -433,7 +432,7 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi JetsnackText( text = formatPrice(shippingCosts), style = { - jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) }, modifier = Modifier.alignBy(LastBaseline), ) @@ -444,7 +443,7 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi JetsnackText( text = stringResource(R.string.cart_total_label), style = { - jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) + jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) }, modifier = Modifier .weight(1f) @@ -455,7 +454,7 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi JetsnackText( text = formatPrice(subtotal + shippingCosts), style = { - jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) }, modifier = Modifier.alignBy(LastBaseline), ) 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 47dce9c495..b545fc45af 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,7 +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.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -49,7 +48,7 @@ import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.components.SnackImage import com.example.jetsnack.ui.components.VerticalGrid import com.example.jetsnack.ui.theme.JetsnackTheme -import com.example.jetsnack.ui.theme.LocalJetsnackColors +import com.example.jetsnack.ui.theme.currentJetsnackTheme import kotlin.math.max @Composable @@ -68,8 +67,8 @@ private fun SearchCategoryCollection(collection: SearchCategoryCollection, index JetsnackText( text = collection.name, style = { - jetsnackTextStyle(LocalTypography.currentValue.titleLarge) - contentColor(LocalJetsnackColors.currentValue.textPrimary) + jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) + contentColor(currentJetsnackTheme.colors.textPrimary) }, modifier = Modifier .heightIn(min = 56.dp) @@ -110,8 +109,8 @@ private fun SearchCategory(category: SearchCategory, gradient: List, modi JetsnackText( text = category.name, style = { - jetsnackTextStyle(LocalTypography.currentValue.titleMedium) - contentColor(LocalJetsnackColors.currentValue.textSecondary) + jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) + contentColor(currentJetsnackTheme.colors.textSecondary) }, modifier = Modifier .padding(4.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 e802d9ec34..53f810ede7 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 @@ -34,7 +34,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.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -53,7 +52,7 @@ import com.example.jetsnack.ui.components.JetsnackText import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.components.SnackImage import com.example.jetsnack.ui.theme.JetsnackTheme -import com.example.jetsnack.ui.theme.LocalJetsnackColors +import com.example.jetsnack.ui.theme.currentJetsnackTheme import com.example.jetsnack.ui.utils.formatPrice @Composable @@ -62,8 +61,8 @@ fun SearchResults(searchResults: List, onSnackClick: (Long, String) -> Un JetsnackText( text = stringResource(R.string.search_count, searchResults.size), style = { - jetsnackTextStyle(LocalTypography.currentValue.titleLarge) - contentColor(LocalJetsnackColors.currentValue.textPrimary) + jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) + contentColor(currentJetsnackTheme.colors.textPrimary) }, modifier = Modifier.padding(horizontal = 24.dp, vertical = 4.dp), ) @@ -105,23 +104,23 @@ private fun SearchResult(snack: Snack, onSnackClick: (Long, String) -> Unit, sho JetsnackText( text = snack.name, style = { - jetsnackTextStyle(LocalTypography.currentValue.titleMedium) - contentColor(LocalJetsnackColors.currentValue.textSecondary) + jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) + contentColor(currentJetsnackTheme.colors.textSecondary) }, ) JetsnackText( text = snack.tagline, style = { - jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) - contentColor(LocalJetsnackColors.currentValue.textHelp) + jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) + contentColor(currentJetsnackTheme.colors.textHelp) }, ) Spacer(Modifier.height(8.dp)) JetsnackText( text = formatPrice(snack.price), style = { - jetsnackTextStyle(LocalTypography.currentValue.titleMedium) - contentColor(LocalJetsnackColors.currentValue.textPrimary) + jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) + contentColor(currentJetsnackTheme.colors.textPrimary) }, ) } @@ -159,7 +158,7 @@ fun NoResults(query: String, modifier: Modifier = Modifier) { JetsnackText( text = stringResource(R.string.search_no_matches, query), style = { - jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) textAlign(TextAlign.Center) }, modifier = Modifier.fillMaxWidth(), @@ -168,7 +167,7 @@ fun NoResults(query: String, modifier: Modifier = Modifier) { JetsnackText( text = stringResource(R.string.search_no_matches_retry), style = { - jetsnackTextStyle(LocalTypography.currentValue.bodyMedium) + jetsnackTextStyle(currentJetsnackTheme.typography.bodyMedium) textAlign(TextAlign.Center) }, modifier = Modifier.fillMaxWidth(), 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 823322656c..768b9da838 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.LocalShapes -import androidx.compose.material3.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable @@ -62,7 +60,7 @@ import com.example.jetsnack.ui.components.JetsnackDivider import com.example.jetsnack.ui.components.JetsnackSurface import com.example.jetsnack.ui.components.JetsnackText import com.example.jetsnack.ui.theme.JetsnackTheme -import com.example.jetsnack.ui.theme.LocalJetsnackColors +import com.example.jetsnack.ui.theme.currentJetsnackTheme @Composable fun Search(onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, state: SearchState = rememberSearchState()) { @@ -173,9 +171,9 @@ private fun SearchBar( ) { JetsnackSurface( style = { - background(LocalJetsnackColors.currentValue.uiFloated) - contentColor(LocalJetsnackColors.currentValue.textSecondary) - shape(LocalShapes.currentValue.small) + background(currentJetsnackTheme.colors.uiFloated) + contentColor(currentJetsnackTheme.colors.textSecondary) + shape(currentJetsnackTheme.shapes.small) }, modifier = modifier .fillMaxWidth() @@ -244,7 +242,7 @@ private fun SearchHint() { JetsnackText( text = stringResource(R.string.search_jetsnack), style = { - contentColor(LocalJetsnackColors.currentValue.textHelp) + contentColor(currentJetsnackTheme.colors.textHelp) } ) } 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 306a2f29a7..40d122e71a 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,7 +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.LocalTypography import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -38,7 +37,7 @@ import com.example.jetsnack.ui.components.JetsnackSurface import com.example.jetsnack.ui.components.JetsnackText import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.theme.JetsnackTheme -import com.example.jetsnack.ui.theme.LocalJetsnackColors +import com.example.jetsnack.ui.theme.currentJetsnackTheme @Composable fun SearchSuggestions(suggestions: List, onSuggestionSelect: (String) -> Unit) { @@ -66,8 +65,8 @@ private fun SuggestionHeader(name: String, modifier: Modifier = Modifier) { JetsnackText( text = name, style = { - jetsnackTextStyle(LocalTypography.currentValue.titleLarge) - contentColor(LocalJetsnackColors.currentValue.textPrimary) + jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) + contentColor(currentJetsnackTheme.colors.textPrimary) }, modifier = modifier .heightIn(min = 56.dp) @@ -81,7 +80,7 @@ private fun Suggestion(suggestion: String, onSuggestionSelect: (String) -> Unit, JetsnackText( text = suggestion, style = { - jetsnackTextStyle(LocalTypography.currentValue.titleMedium) + jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) }, modifier = modifier .heightIn(min = 48.dp) 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 55d33fadec..fedc561b2e 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 @@ -65,7 +65,6 @@ 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.LocalTypography import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -114,7 +113,7 @@ 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.theme.JetsnackTheme -import com.example.jetsnack.ui.theme.LocalJetsnackColors +import com.example.jetsnack.ui.theme.currentJetsnackTheme import com.example.jetsnack.ui.theme.Neutral8 import com.example.jetsnack.ui.utils.formatPrice import kotlin.math.max @@ -310,8 +309,8 @@ private fun Body(related: List, scroll: ScrollState) { JetsnackText( text = stringResource(R.string.detail_header), style = { - jetsnackTextStyle(LocalTypography.currentValue.headlineMedium) - contentColor(LocalJetsnackColors.currentValue.textHelp) + jetsnackTextStyle(currentJetsnackTheme.typography.headlineMedium) + contentColor(currentJetsnackTheme.colors.textHelp) contentPaddingHorizontal(24.dp) } ) @@ -321,8 +320,8 @@ private fun Body(related: List, scroll: ScrollState) { JetsnackText( text = stringResource(R.string.detail_placeholder), style = { - jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) - contentColor(LocalJetsnackColors.currentValue.textHelp) + jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) + contentColor(currentJetsnackTheme.colors.textHelp) contentPaddingHorizontal(24.dp) }, maxLines = if (seeMore) 5 else Int.MAX_VALUE, @@ -339,8 +338,8 @@ private fun Body(related: List, scroll: ScrollState) { JetsnackText( text = textButton, style = { - jetsnackTextStyle(LocalTypography.currentValue.labelLarge) - contentColor(LocalJetsnackColors.currentValue.textLink) + jetsnackTextStyle(currentJetsnackTheme.typography.labelLarge) + contentColor(currentJetsnackTheme.colors.textLink) textAlign(TextAlign.Center) contentPaddingTop(15.dp) fillWidth() @@ -357,8 +356,8 @@ private fun Body(related: List, scroll: ScrollState) { JetsnackText( text = stringResource(R.string.ingredients), style = { - jetsnackTextStyle(LocalTypography.currentValue.labelSmall) - contentColor(LocalJetsnackColors.currentValue.textHelp) + jetsnackTextStyle(currentJetsnackTheme.typography.labelSmall) + contentColor(currentJetsnackTheme.colors.textHelp) contentPaddingHorizontal(24.dp) } ) @@ -366,8 +365,8 @@ private fun Body(related: List, scroll: ScrollState) { JetsnackText( text = stringResource(R.string.ingredients_list), style = { - jetsnackTextStyle(LocalTypography.currentValue.bodyLarge) - contentColor(LocalJetsnackColors.currentValue.textHelp) + jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) + contentColor(currentJetsnackTheme.colors.textHelp) contentPaddingHorizontal(24.dp) } ) @@ -425,8 +424,8 @@ private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { JetsnackText( text = snack.name, style = { - jetsnackTextStyle(LocalTypography.currentValue.headlineMedium) - contentColor(LocalJetsnackColors.currentValue.textSecondary) + jetsnackTextStyle(currentJetsnackTheme.typography.headlineMedium) + contentColor(currentJetsnackTheme.colors.textSecondary) fontStyle(FontStyle.Italic) }, modifier = HzPadding @@ -446,9 +445,9 @@ private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { JetsnackText( text = snack.tagline, style = { - jetsnackTextStyle(LocalTypography.currentValue.titleSmall) + jetsnackTextStyle(currentJetsnackTheme.typography.titleSmall) fontStyle(FontStyle.Italic) - contentColor(LocalJetsnackColors.currentValue.textHelp) + contentColor(currentJetsnackTheme.colors.textHelp) fontSize(20.sp) }, modifier = HzPadding @@ -470,8 +469,8 @@ private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { JetsnackText( text = formatPrice(snack.price), style = { - jetsnackTextStyle(LocalTypography.currentValue.titleLarge) - contentColor(LocalJetsnackColors.currentValue.textPrimary) + jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) + contentColor(currentJetsnackTheme.colors.textPrimary) }, modifier = HzPadding .animateEnterExit( 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 eb99c98163..04237b42c4 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,56 @@ 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, +) \ No newline at end of file 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 index 741ce488b9..67a8ac37c7 100644 --- 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 @@ -6,63 +6,60 @@ 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.StyleScope import androidx.compose.foundation.style.disabled import androidx.compose.foundation.style.fillWidth -import androidx.compose.foundation.style.hovered import androidx.compose.foundation.style.pressed import androidx.compose.foundation.style.selected import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.LocalShapes import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.LocalTypography -import androidx.compose.material3.Typography import androidx.compose.runtime.Immutable -import androidx.compose.runtime.ProvidableCompositionLocal 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.jetsnackTextStyle +import com.example.jetsnack.ui.theme.JetsnackTheme.Companion.LocalJetsnackTheme @Immutable data class AppStyles( val buttonStyle : Style = Style { shape( RoundedCornerShape(percent = 50)) - background(Brush.linearGradient(LocalJetsnackColors.currentValue.interactivePrimary)) - contentColor(LocalJetsnackColors.currentValue.textInteractive) + background(Brush.linearGradient(currentJetsnackTheme.colors.interactivePrimary)) + contentColor(currentJetsnackTheme.colors.textInteractive) contentPadding( ButtonDefaults.ContentPadding.calculateTopPadding()) // todo file issue to support padding values - jetsnackTextStyle(LocalTypography.currentValue.labelLarge) + jetsnackTextStyle(currentJetsnackTheme.typography.labelLarge) disabled { animate { - background(Brush.linearGradient(LocalJetsnackColors.currentValue.interactiveSecondary)) - contentColor(LocalJetsnackColors.currentValue.textHelp) + background(Brush.linearGradient(currentJetsnackTheme.colors.interactiveSecondary)) + contentColor(currentJetsnackTheme.colors.textHelp) } } }, val cardStyle: Style = Style { - shape(LocalShapes.currentValue.medium) - background(LocalJetsnackColors.currentValue.uiBackground) - contentColor(LocalJetsnackColors.currentValue.textPrimary) + shape(currentJetsnackTheme.shapes.medium) + background(currentJetsnackTheme.colors.uiBackground) + contentColor(currentJetsnackTheme.colors.textPrimary) /* todo elevation elevation: Dp = 4.dp,*/ }, val dividerStyle: Style = Style { - background(LocalJetsnackColors.currentValue.uiBorder.copy(alpha = 0.12f)) + background(currentJetsnackTheme.colors.uiBorder.copy(alpha = 0.12f)) height(1.dp) fillWidth() }, val gradientIconButtonStyle: Style = Style { shape(CircleShape) clip(true) - border(2.dp, Brush.linearGradient(LocalJetsnackColors.currentValue.interactiveSecondary)) - background(LocalJetsnackColors.currentValue.uiBackground) + border(2.dp, Brush.linearGradient(currentJetsnackTheme.colors.interactiveSecondary)) + background(currentJetsnackTheme.colors.uiBackground) pressed { animate { background(Brush.horizontalGradient( // this was a parameter input into the function? might want to make helper function for it - colors = LocalJetsnackColors.currentValue.interactiveSecondary, + colors = currentJetsnackTheme.colors.interactiveSecondary, startX = 0f, endX = 200f, tileMode = TileMode.Mirror, @@ -71,14 +68,14 @@ data class AppStyles( } }, val filterChipStyle: Style = Style { - shape(LocalShapes.currentValue.small) - background(LocalJetsnackColors.currentValue.uiBackground) - contentColor(LocalJetsnackColors.currentValue.textSecondary) - border(1.dp, Brush.linearGradient(LocalJetsnackColors.currentValue.interactiveSecondary)) + shape(currentJetsnackTheme.shapes.small) + background(currentJetsnackTheme.colors.uiBackground) + contentColor(currentJetsnackTheme.colors.textSecondary) + border(1.dp, Brush.linearGradient(currentJetsnackTheme.colors.interactiveSecondary)) // todo elevation = 2.dp, selected { animate { - background(LocalJetsnackColors.currentValue.brandSecondary) + background(currentJetsnackTheme.colors.brandSecondary) contentColor(Color.Black) border(1.dp, Color.Transparent) } @@ -89,8 +86,10 @@ data class AppStyles( }, val surfaceStyle: Style = Style { shape(RectangleShape) - background(LocalJetsnackColors.currentValue.uiBackground) - contentColor(LocalJetsnackColors.currentValue.textSecondary) + background(currentJetsnackTheme.colors.uiBackground) + contentColor(currentJetsnackTheme.colors.textSecondary) clip(true) } ) +val StyleScope.currentJetsnackTheme: JetsnackTheme + get() = LocalJetsnackTheme.currentValue \ No newline at end of file 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 2ad96a0f0a..008a18b966 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 @@ -20,176 +20,60 @@ package com.example.jetsnack.ui.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.style.ExperimentalFoundationStyleApi -import androidx.compose.material3.ColorScheme 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, -) -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, -) +@Immutable +class JetsnackTheme( + val colors: JetsnackColors = LightColorPalette, + val typography: Typography = Typography, + val appStyles: AppStyles = AppStyles(), +) { + val shapes: Shapes = _shapes -@Composable -fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { - val colors = if (darkTheme) DarkColorPalette else LightColorPalette - val styles = AppStyles() - ProvideJetsnackColors(colors) { - CompositionLocalProvider(LocalAppStyles provides styles) { - MaterialTheme( - colorScheme = debugColors(darkTheme), - typography = Typography, - shapes = Shapes, - content = content, - ) - } + 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 + + private val _shapes = Shapes } } -//todo discuss best practise for exposing composition locals with @Composable, vs ProvideCompositionLocal -object JetsnackTheme { - val colors: JetsnackColors - @Composable - get() = LocalJetsnackColors.current - val appStyles : AppStyles - @Composable - get() = LocalAppStyles.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, -) +internal val LocalJetsnackThemeInstance = staticCompositionLocalOf { JetsnackTheme() } @Composable -fun ProvideJetsnackColors(colors: JetsnackColors, content: @Composable () -> Unit) { - CompositionLocalProvider(LocalJetsnackColors provides colors, content = content) -} +fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { + val colors = if (darkTheme) DarkColorPalette else LightColorPalette + val theme = JetsnackTheme(colors = colors, appStyles = AppStyles()) -val LocalJetsnackColors = staticCompositionLocalOf { - error("No JetsnackColorPalette provided") + CompositionLocalProvider( + JetsnackTheme.LocalJetsnackTheme provides theme + ) { + MaterialTheme( + colorScheme = debugColors(darkTheme), + typography = Typography, + shapes = Shapes, + content = content, + ) + } } -val LocalAppStyles = staticCompositionLocalOf { AppStyles() } - -/** - * 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, -) From 6a3885ce20ddc46ce36bacae9e54a38d34cf2d30 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 16 Mar 2026 13:01:24 +0000 Subject: [PATCH 04/33] Run Spotless --- .../example/jetsnack/ui/components/Button.kt | 2 +- .../example/jetsnack/ui/components/Card.kt | 6 +-- .../example/jetsnack/ui/components/Divider.kt | 6 +-- .../example/jetsnack/ui/components/Filters.kt | 6 +-- .../jetsnack/ui/components/Gradient.kt | 1 - .../example/jetsnack/ui/components/Snacks.kt | 2 +- .../example/jetsnack/ui/components/Surface.kt | 53 ------------------- .../example/jetsnack/ui/components/Text.kt | 20 ++++++- .../example/jetsnack/ui/home/FilterScreen.kt | 6 +-- .../java/com/example/jetsnack/ui/home/Home.kt | 4 +- .../com/example/jetsnack/ui/home/cart/Cart.kt | 4 +- .../jetsnack/ui/home/search/Categories.kt | 2 +- .../jetsnack/ui/home/search/Results.kt | 2 +- .../example/jetsnack/ui/home/search/Search.kt | 2 +- .../jetsnack/ui/snackdetail/SnackDetail.kt | 10 ++-- .../com/example/jetsnack/ui/theme/Color.kt | 5 +- .../com/example/jetsnack/ui/theme/Styles.kt | 42 ++++++++++----- .../com/example/jetsnack/ui/theme/Theme.kt | 15 +++--- 18 files changed, 79 insertions(+), 109 deletions(-) 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 45c2622aad..4a4a97c33c 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 @@ -43,7 +43,7 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview import com.example.jetsnack.ui.theme.JetsnackTheme -//todo think about the text style here +// todo think about the text style here @Composable fun JetsnackButton( onClick: () -> Unit, 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 e8f4f2bf35..cbe20f493e 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 @@ -30,11 +30,7 @@ import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.theme.JetsnackTheme @Composable -fun JetsnackCard( - modifier: Modifier = Modifier, - style: Style = Style, - content: @Composable () -> Unit, -) { +fun JetsnackCard(modifier: Modifier = Modifier, style: Style = Style, content: @Composable () -> Unit) { JetsnackSurface( modifier = modifier, style = JetsnackTheme.appStyles.cardStyle then style, 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 88b35269a4..8a14dcb806 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 @@ -34,14 +34,10 @@ import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.theme.JetsnackTheme.Companion.LocalJetsnackTheme @Composable -fun JetsnackDivider( - modifier: Modifier = Modifier, - style: Style = Style -) { +fun JetsnackDivider(modifier: Modifier = Modifier, style: Style = Style) { Box(modifier = modifier.styleable(null, LocalJetsnackTheme.current.appStyles.dividerStyle, style)) } - @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 92b11cbedd..09087a7e5f 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 @@ -107,11 +107,7 @@ fun FilterBar( } @Composable -fun FilterChip( - filter: Filter, - modifier: Modifier = Modifier, - style: Style = Style, -) { +fun FilterChip(filter: Filter, modifier: Modifier = Modifier, style: Style = Style) { val (selected, setSelected) = filter.enabled val interactionSource = remember { MutableInteractionSource() } 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 58c94ef17b..5709b8cecd 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 @@ -55,4 +55,3 @@ fun Modifier.diagonalGradientBorder(colors: List, borderSize: Dp = 2.dp, brush = Brush.linearGradient(colors), shape = shape, ) - 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 b0423c0fab..fa56c5be65 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 @@ -288,7 +288,7 @@ private fun HighlightSnackItem( } JetsnackCard( style = Style { - shape( RoundedCornerShape(roundedCornerAnimation)) + shape(RoundedCornerShape(roundedCornerAnimation)) }, modifier = modifier .padding(bottom = 16.dp) 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 1346999427..46aa30409a 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 @@ -18,30 +18,15 @@ 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.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.material3.LocalContentColor 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 @@ -63,41 +48,3 @@ fun JetsnackSurface( content() } } - -/* TODO Migrate computed property -@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 - ) { - color.withElevation(elevation) - } else { - color - } -} -*/ - -/** - * 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 index 2b3a9af21b..37e68dd3af 100644 --- 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 @@ -1,3 +1,19 @@ +/* + * 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 @@ -8,8 +24,8 @@ 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.TextStyle 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 @@ -32,7 +48,7 @@ fun JetsnackText( maxLines: Int = Int.MAX_VALUE, minLines: Int = 1, autoSize: TextAutoSize? = null, - ) { +) { BasicText( text = text, modifier = modifier.styleable(null, JetsnackTheme.appStyles.defaultTextStyle, style), 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 7bd9e102d1..0126912b91 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 @@ -144,7 +144,7 @@ fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilit style = { textAlign(TextAlign.Center) jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) - } + }, ) val resetEnabled = sortState != defaultFilter @@ -165,9 +165,9 @@ fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilit fontWeight(fontWeight) contentColor( currentJetsnackTheme.colors.uiBackground - .copy(alpha = if (!resetEnabled) 0.38f else 1f) + .copy(alpha = if (!resetEnabled) 0.38f else 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 ada5b2abd0..59d3f26019 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 @@ -58,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 @@ -83,7 +84,6 @@ import com.example.jetsnack.ui.snackdetail.spatialExpressiveSpring import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.theme.currentJetsnackTheme import java.util.Locale -import androidx.compose.ui.platform.LocalLocale fun NavGraphBuilder.composableWithCompositionLocal( route: String, @@ -179,7 +179,7 @@ fun JetsnackBottomBar( style = { background(color) contentColor(contentColor) - } + }, ) { val springSpec = spatialExpressiveSpring() JetsnackBottomNavLayout( 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 a638c1b790..66df1f0820 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 @@ -56,6 +56,7 @@ 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 @@ -74,10 +75,10 @@ import com.example.jetsnack.ui.components.JetsnackButton import com.example.jetsnack.ui.components.JetsnackDivider import com.example.jetsnack.ui.components.JetsnackSurface import com.example.jetsnack.ui.components.JetsnackText -import com.example.jetsnack.ui.components.jetsnackTextStyle 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.jetsnackTextStyle import com.example.jetsnack.ui.home.DestinationBar import com.example.jetsnack.ui.snackdetail.nonSpatialExpressiveSpring import com.example.jetsnack.ui.snackdetail.spatialExpressiveSpring @@ -86,7 +87,6 @@ import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.theme.currentJetsnackTheme import com.example.jetsnack.ui.utils.formatPrice import kotlin.math.roundToInt -import androidx.compose.ui.platform.LocalResources @Composable fun Cart( 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 b545fc45af..5238998a94 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 @@ -44,9 +44,9 @@ import com.example.jetsnack.R import com.example.jetsnack.model.SearchCategory import com.example.jetsnack.model.SearchCategoryCollection import com.example.jetsnack.ui.components.JetsnackText -import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.components.SnackImage import com.example.jetsnack.ui.components.VerticalGrid +import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.theme.currentJetsnackTheme import kotlin.math.max 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 53f810ede7..852655b670 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 @@ -49,8 +49,8 @@ import com.example.jetsnack.ui.components.JetsnackButton import com.example.jetsnack.ui.components.JetsnackDivider import com.example.jetsnack.ui.components.JetsnackSurface import com.example.jetsnack.ui.components.JetsnackText -import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.components.SnackImage +import com.example.jetsnack.ui.components.jetsnackTextStyle import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.theme.currentJetsnackTheme import com.example.jetsnack.ui.utils.formatPrice 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 768b9da838..ff24a92711 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 @@ -243,7 +243,7 @@ private fun SearchHint() { text = stringResource(R.string.search_jetsnack), style = { contentColor(currentJetsnackTheme.colors.textHelp) - } + }, ) } } 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 fedc561b2e..a6c35c6f65 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 @@ -108,13 +108,13 @@ 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.JetsnackText -import com.example.jetsnack.ui.components.jetsnackTextStyle 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.jetsnackTextStyle import com.example.jetsnack.ui.theme.JetsnackTheme -import com.example.jetsnack.ui.theme.currentJetsnackTheme import com.example.jetsnack.ui.theme.Neutral8 +import com.example.jetsnack.ui.theme.currentJetsnackTheme import com.example.jetsnack.ui.utils.formatPrice import kotlin.math.max import kotlin.math.min @@ -312,7 +312,7 @@ private fun Body(related: List, scroll: ScrollState) { jetsnackTextStyle(currentJetsnackTheme.typography.headlineMedium) contentColor(currentJetsnackTheme.colors.textHelp) contentPaddingHorizontal(24.dp) - } + }, ) Spacer(Modifier.height(16.dp)) var seeMore by remember { mutableStateOf(true) } @@ -359,7 +359,7 @@ private fun Body(related: List, scroll: ScrollState) { jetsnackTextStyle(currentJetsnackTheme.typography.labelSmall) contentColor(currentJetsnackTheme.colors.textHelp) contentPaddingHorizontal(24.dp) - } + }, ) Spacer(Modifier.height(4.dp)) JetsnackText( @@ -368,7 +368,7 @@ private fun Body(related: List, scroll: ScrollState) { jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) contentColor(currentJetsnackTheme.colors.textHelp) contentPaddingHorizontal(24.dp) - } + }, ) Spacer(Modifier.height(16.dp)) 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 04237b42c4..918312fb87 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 @@ -108,7 +108,7 @@ fun debugColors(darkTheme: Boolean, debugColor: Color = Color.Magenta) = ColorSc tertiaryFixed = debugColor, tertiaryFixedDim = debugColor, onTertiaryFixed = debugColor, - onTertiaryFixedVariant = debugColor + onTertiaryFixedVariant = debugColor, ) val Shadow11 = Color(0xff001787) @@ -181,7 +181,6 @@ val FunctionalDarkGrey = Color(0xff2e2e2e) const val AlphaNearOpaque = 0.95f - internal val LightColorPalette = JetsnackColors( brand = Shadow5, brandSecondary = Ocean3, @@ -232,4 +231,4 @@ internal val DarkColorPalette = JetsnackColors( gradient2_3 = listOf(Lavender3, Rose3), tornado1 = listOf(Shadow4, Ocean3), isDark = true, -) \ No newline at end of file +) 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 index 67a8ac37c7..9b6012b704 100644 --- 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 @@ -1,3 +1,19 @@ +/* + * 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 @@ -24,11 +40,11 @@ import com.example.jetsnack.ui.theme.JetsnackTheme.Companion.LocalJetsnackTheme @Immutable data class AppStyles( - val buttonStyle : Style = Style { - shape( RoundedCornerShape(percent = 50)) + val buttonStyle: Style = Style { + shape(RoundedCornerShape(percent = 50)) background(Brush.linearGradient(currentJetsnackTheme.colors.interactivePrimary)) contentColor(currentJetsnackTheme.colors.textInteractive) - contentPadding( ButtonDefaults.ContentPadding.calculateTopPadding()) // todo file issue to support padding values + contentPadding(ButtonDefaults.ContentPadding.calculateTopPadding()) // todo file issue to support padding values jetsnackTextStyle(currentJetsnackTheme.typography.labelLarge) disabled { animate { @@ -57,13 +73,15 @@ data class AppStyles( background(currentJetsnackTheme.colors.uiBackground) pressed { animate { - background(Brush.horizontalGradient( - // this was a parameter input into the function? might want to make helper function for it - colors = currentJetsnackTheme.colors.interactiveSecondary, - startX = 0f, - endX = 200f, - tileMode = TileMode.Mirror, - )) + background( + Brush.horizontalGradient( + // this was a parameter input into the function? might want to make helper function for it + colors = currentJetsnackTheme.colors.interactiveSecondary, + startX = 0f, + endX = 200f, + tileMode = TileMode.Mirror, + ), + ) } } }, @@ -89,7 +107,7 @@ data class AppStyles( background(currentJetsnackTheme.colors.uiBackground) contentColor(currentJetsnackTheme.colors.textSecondary) clip(true) - } + }, ) val StyleScope.currentJetsnackTheme: JetsnackTheme - get() = LocalJetsnackTheme.currentValue \ No newline at end of file + get() = LocalJetsnackTheme.currentValue 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 008a18b966..4b19ef0080 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 @@ -30,7 +30,6 @@ import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.staticCompositionLocalOf - @Immutable class JetsnackTheme( val colors: JetsnackColors = LightColorPalette, @@ -41,16 +40,20 @@ class JetsnackTheme( companion object { val colors: JetsnackColors - @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.colors + @Composable @ReadOnlyComposable + get() = LocalJetsnackTheme.current.colors val typography: Typography - @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.typography + @Composable @ReadOnlyComposable + get() = LocalJetsnackTheme.current.typography val shapes: Shapes - @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.shapes + @Composable @ReadOnlyComposable + get() = LocalJetsnackTheme.current.shapes val appStyles: AppStyles - @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.appStyles + @Composable @ReadOnlyComposable + get() = LocalJetsnackTheme.current.appStyles val LocalJetsnackTheme: ProvidableCompositionLocal get() = LocalJetsnackThemeInstance @@ -67,7 +70,7 @@ fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composab val theme = JetsnackTheme(colors = colors, appStyles = AppStyles()) CompositionLocalProvider( - JetsnackTheme.LocalJetsnackTheme provides theme + JetsnackTheme.LocalJetsnackTheme provides theme, ) { MaterialTheme( colorScheme = debugColors(darkTheme), From 169312e0c1b385017983cd841c9332c162a033ea Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 16 Mar 2026 18:07:29 +0000 Subject: [PATCH 05/33] Rename and refactor UI components and theme accessors. - Rename `JetsnackButton`, `JetsnackSurface`, and `JetsnackText` to `Button`, `Surface`, and `Text`. - Rename `jetsnackTextStyle` to `textStyleWithFontFamilyFix`. - Replace `currentJetsnackTheme` with direct `colors`, `typography`, and `shapes` accessors within `StyleScope`. - Update `AppStyles` to use the new theme accessors and refined padding properties. - Add a ripple indication to the base `Button` component. --- .../example/jetsnack/ui/components/Button.kt | 43 ++++------ .../example/jetsnack/ui/components/Card.kt | 4 +- .../example/jetsnack/ui/components/Filters.kt | 14 ++-- .../ui/components/GradientTintedIconButton.kt | 10 ++- .../ui/components/QuantitySelector.kt | 19 ++--- .../example/jetsnack/ui/components/Snacks.kt | 34 ++++---- .../example/jetsnack/ui/components/Surface.kt | 2 +- .../example/jetsnack/ui/components/Text.kt | 4 +- .../jetsnack/ui/home/DestinationBar.kt | 13 +-- .../java/com/example/jetsnack/ui/home/Feed.kt | 4 +- .../example/jetsnack/ui/home/FilterScreen.kt | 33 ++++---- .../java/com/example/jetsnack/ui/home/Home.kt | 14 ++-- .../com/example/jetsnack/ui/home/Profile.kt | 16 ++-- .../com/example/jetsnack/ui/home/cart/Cart.kt | 80 +++++++++---------- .../jetsnack/ui/home/search/Categories.kt | 19 ++--- .../jetsnack/ui/home/search/Results.kt | 48 +++++------ .../example/jetsnack/ui/home/search/Search.kt | 23 +++--- .../jetsnack/ui/home/search/Suggestions.kt | 21 ++--- .../jetsnack/ui/snackdetail/SnackDetail.kt | 68 ++++++++-------- .../com/example/jetsnack/ui/theme/Styles.kt | 50 ++++++------ .../com/example/jetsnack/ui/theme/Theme.kt | 15 +++- 21 files changed, 271 insertions(+), 263 deletions(-) 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 4a4a97c33c..b5fc023b37 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 @@ -20,7 +20,6 @@ package com.example.jetsnack.ui.components import android.content.res.Configuration.UI_MODE_NIGHT_YES 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.Row @@ -31,8 +30,6 @@ 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.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -43,9 +40,8 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview import com.example.jetsnack.ui.theme.JetsnackTheme -// todo think about the text style here @Composable -fun JetsnackButton( +fun Button( onClick: () -> Unit, modifier: Modifier = Modifier, style: Style = Style, @@ -56,7 +52,7 @@ fun JetsnackButton( val styleState = rememberUpdatedStyleState(interactionSource, { it.isEnabled = enabled }) - JetsnackSurface( + Surface( style = JetsnackTheme.appStyles.buttonStyle then style, styleState = styleState, modifier = modifier @@ -65,24 +61,19 @@ fun JetsnackButton( 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()), - 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, + ) } } @@ -92,8 +83,8 @@ fun JetsnackButton( @Composable private fun ButtonPreview() { JetsnackTheme { - JetsnackButton(onClick = {}) { - JetsnackText(text = "Demo") + Button(onClick = {}) { + Text(text = "Demo") } } } @@ -104,12 +95,12 @@ private fun ButtonPreview() { @Composable private fun RectangleButtonPreview() { JetsnackTheme { - JetsnackButton( + Button( onClick = {}, style = { shape(RectangleShape) }, ) { - JetsnackText(text = "Demo") + 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 cbe20f493e..dc8c4041e4 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 @@ -31,7 +31,7 @@ import com.example.jetsnack.ui.theme.JetsnackTheme @Composable fun JetsnackCard(modifier: Modifier = Modifier, style: Style = Style, content: @Composable () -> Unit) { - JetsnackSurface( + Surface( modifier = modifier, style = JetsnackTheme.appStyles.cardStyle then style, content = content, @@ -45,7 +45,7 @@ fun JetsnackCard(modifier: Modifier = Modifier, style: Style = Style, content: @ private fun CardPreview() { JetsnackTheme { JetsnackCard { - JetsnackText(text = "Demo", modifier = Modifier.padding(16.dp)) + Text(text = "Demo", modifier = Modifier.padding(16.dp)) } } } 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 09087a7e5f..58dc08f5a8 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 @@ -55,7 +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.currentJetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.shapes +import com.example.jetsnack.ui.theme.typography @Composable fun FilterBar( @@ -98,7 +100,7 @@ fun FilterBar( FilterChip( filter = filter, style = Style { - shape(currentJetsnackTheme.shapes.small) + shape(shapes.small) }, ) } @@ -118,7 +120,7 @@ fun FilterChip(filter: Filter, modifier: Modifier = Modifier, style: Style = Sty }, ) - JetsnackSurface( + Surface( modifier = modifier .toggleable( value = selected, @@ -135,7 +137,7 @@ fun FilterChip(filter: Filter, modifier: Modifier = Modifier, style: Style = Sty animate { background( Brush.horizontalGradient( - colors = currentJetsnackTheme.colors.interactiveSecondary, + colors = colors.interactiveSecondary, startX = 0f, endX = 200f, tileMode = TileMode.Mirror, @@ -148,10 +150,10 @@ fun FilterChip(filter: Filter, modifier: Modifier = Modifier, style: Style = Sty modifier = Modifier .styleable(styleState, innerBackgroundStyle), ) { - JetsnackText( + Text( text = filter.name, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodySmall) + textStyleWithFontFamilyFix(typography.bodySmall) }, maxLines = 1, modifier = Modifier.padding( 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 c65086d21e..0b73d55bde 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 @@ -23,15 +23,19 @@ import androidx.annotation.DrawableRes 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.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.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color @@ -72,8 +76,8 @@ fun JetsnackGradientTintedIconButton( val modifierColor = if (pressed) { Modifier.contentTintDiagonalGradient( colors = listOf( - JetsnackTheme.colors.textSecondary, - JetsnackTheme.colors.textSecondary, + JetsnackTheme.colors.textPrimary, + JetsnackTheme.colors.textPrimary, ), blendMode = blendMode, ) @@ -103,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 a8e15c37cc..6940051e9b 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 @@ -38,16 +38,17 @@ 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.currentJetsnackTheme +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) { - JetsnackText( + Text( text = stringResource(R.string.quantity), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) - contentColor(currentJetsnackTheme.colors.textSecondary) + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textSecondary) fontWeight(FontWeight.Normal) }, modifier = Modifier @@ -65,12 +66,12 @@ fun QuantitySelector(count: Int, decreaseItemCount: () -> Unit, increaseItemCoun modifier = Modifier .align(Alignment.CenterVertically), ) { - JetsnackText( + Text( text = "$it", style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleSmall) + textStyleWithFontFamilyFix(typography.titleSmall) fontSize(18.sp) - contentColor(currentJetsnackTheme.colors.textPrimary) + contentColor(colors.textPrimary) textAlign(TextAlign.Center) }, modifier = Modifier.widthIn(min = 24.dp), @@ -91,7 +92,7 @@ fun QuantitySelector(count: Int, decreaseItemCount: () -> Unit, increaseItemCoun @Composable fun QuantitySelectorPreview() { JetsnackTheme { - JetsnackSurface { + Surface { QuantitySelector(1, {}, {}) } } @@ -101,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 fa56c5be65..b77f6935fb 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 @@ -83,7 +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.currentJetsnackTheme +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 @@ -105,11 +107,11 @@ fun SnackCollection( .heightIn(min = 56.dp) .padding(start = 24.dp), ) { - JetsnackText( + Text( text = snackCollection.name, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) - contentColor(currentJetsnackTheme.colors.brand) + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.brand) }, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -191,9 +193,9 @@ private fun Snacks(snackCollectionId: Long, snacks: List, onSnackClick: ( @Composable fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier) { - JetsnackSurface( + Surface( style = { - shape(currentJetsnackTheme.shapes.medium) + shape(shapes.medium) }, modifier = modifier.padding( start = 4.dp, @@ -234,11 +236,11 @@ fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String boundsTransform = snackDetailBoundsTransform, ), ) - JetsnackText( + Text( text = snack.name, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) - contentColor(currentJetsnackTheme.colors.textSecondary) + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textSecondary) }, modifier = Modifier .padding(top = 8.dp) @@ -393,13 +395,13 @@ private fun HighlightSnackItem( } Spacer(modifier = Modifier.height(8.dp)) - JetsnackText( + Text( text = snack.name, maxLines = 1, overflow = TextOverflow.Ellipsis, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) - contentColor(currentJetsnackTheme.colors.textSecondary) + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.textSecondary) }, modifier = Modifier .padding(horizontal = 16.dp) @@ -421,11 +423,11 @@ private fun HighlightSnackItem( ) Spacer(modifier = Modifier.height(4.dp)) - JetsnackText( + Text( text = snack.tagline, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) - contentColor(currentJetsnackTheme.colors.textHelp) + textStyleWithFontFamilyFix(typography.bodyLarge) + contentColor(colors.textHelp) }, modifier = Modifier .padding(horizontal = 16.dp) @@ -465,7 +467,7 @@ fun SnackImage( modifier: Modifier = Modifier, elevation: Dp = 0.dp, ) { - JetsnackSurface( + Surface( style = { shape(CircleShape) // todo elevation 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 46aa30409a..fbbfd30f71 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 @@ -33,7 +33,7 @@ import com.example.jetsnack.ui.theme.JetsnackTheme * [com.example.jetsnack.ui.theme.JetsnackColors] */ @Composable -fun JetsnackSurface( +fun Surface( modifier: Modifier = Modifier, style: Style = Style, // todo confirm patten is acceptable 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 index 37e68dd3af..f0e374b16e 100644 --- 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 @@ -31,14 +31,14 @@ import com.example.jetsnack.ui.theme.JetsnackTheme // Workaround for b/492528450 - setting textStyle currently doesn't set fontFamily. @ExperimentalFoundationStyleApi -fun StyleScope.jetsnackTextStyle(value: TextStyle) { +fun StyleScope.textStyleWithFontFamilyFix(value: TextStyle) { textStyle(value) value.fontFamily?.let { fontFamily(it) } } @ExperimentalFoundationStyleApi @Composable -fun JetsnackText( +fun Text( text: String, modifier: Modifier = Modifier, style: Style = Style, 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 a986b7743a..b5c85d0df9 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 @@ -44,12 +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.JetsnackText -import com.example.jetsnack.ui.components.jetsnackTextStyle +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.currentJetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.typography @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -72,11 +73,11 @@ fun DestinationBar(modifier: Modifier = Modifier) { windowInsets = WindowInsets(0, 0, 0, 0), title = { Row { - JetsnackText( + Text( text = "Delivery to 1600 Amphitheater Way", style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) - contentColor(currentJetsnackTheme.colors.textSecondary) + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textSecondary) textAlign(TextAlign.Center) }, maxLines = 1, 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 39ccd8e74b..4c9b29cc22 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) } 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 0126912b91..0bf868481c 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 @@ -70,10 +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.JetsnackText -import com.example.jetsnack.ui.components.jetsnackTextStyle +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.currentJetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.typography @Composable fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope, onDismiss: () -> Unit) { @@ -135,7 +136,7 @@ fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilit contentDescription = stringResource(id = R.string.close), ) } - JetsnackText( + Text( text = stringResource(id = R.string.label_filters), modifier = Modifier .fillMaxWidth() @@ -143,7 +144,7 @@ fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilit .padding(top = 8.dp, end = 48.dp), style = { textAlign(TextAlign.Center) - jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) + textStyleWithFontFamilyFix(typography.titleLarge) }, ) val resetEnabled = sortState != defaultFilter @@ -158,13 +159,13 @@ fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilit FontWeight.Normal } - JetsnackText( + Text( text = stringResource(id = R.string.reset), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodyMedium) + textStyleWithFontFamilyFix(typography.bodyMedium) fontWeight(fontWeight) contentColor( - currentJetsnackTheme.colors.uiBackground + colors.uiBackground .copy(alpha = if (!resetEnabled) 0.38f else 1f), ) }, @@ -251,11 +252,11 @@ fun SortFilters(sortFilters: List = SnackRepo.getSortFilters(), sortStat fun MaxCalories(sliderPosition: Float, onValueChanged: (Float) -> Unit) { FlowRow { FilterTitle(text = stringResource(id = R.string.max_calories)) - JetsnackText( + Text( text = stringResource(id = R.string.per_serving), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodyMedium) - contentColor(currentJetsnackTheme.colors.brand) + textStyleWithFontFamilyFix(typography.bodyMedium) + contentColor(colors.brand) }, modifier = Modifier.padding(top = 5.dp, start = 10.dp), ) @@ -279,11 +280,11 @@ fun MaxCalories(sliderPosition: Float, onValueChanged: (Float) -> Unit) { @Composable fun FilterTitle(text: String) { - JetsnackText( + Text( text = text, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) - contentColor(currentJetsnackTheme.colors.brand) + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.brand) }, modifier = Modifier.padding(bottom = 8.dp), ) @@ -299,10 +300,10 @@ fun SortOption(text: String, @DrawableRes icon: Int?, onClickOption: () -> Unit, if (icon != null) { Icon(painter = painterResource(id = icon), contentDescription = null) } - JetsnackText( + Text( text = text, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) + textStyleWithFontFamilyFix(typography.titleMedium) }, modifier = Modifier .padding(start = 10.dp) 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 59d3f26019..d2265268ba 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 @@ -74,15 +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.JetsnackText -import com.example.jetsnack.ui.components.jetsnackTextStyle +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.currentJetsnackTheme +import com.example.jetsnack.ui.theme.typography import java.util.Locale fun NavGraphBuilder.composableWithCompositionLocal( @@ -174,7 +174,7 @@ fun JetsnackBottomBar( val routes = remember { tabs.map { it.route } } val currentSection = tabs.first { it.route == currentRoute } - JetsnackSurface( + Surface( modifier = modifier, style = { background(color) @@ -215,11 +215,11 @@ fun JetsnackBottomBar( ) }, text = { - JetsnackText( + Text( text = text, style = { contentColor(tint) - jetsnackTextStyle(currentJetsnackTheme.typography.labelLarge) + 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 8510712f33..33e5d8ca83 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 @@ -34,10 +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.JetsnackText -import com.example.jetsnack.ui.components.jetsnackTextStyle +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.currentJetsnackTheme +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) { @@ -53,19 +55,19 @@ fun Profile(modifier: Modifier = Modifier) { contentDescription = null, ) Spacer(Modifier.height(24.dp)) - JetsnackText( + Text( text = stringResource(R.string.work_in_progress), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) + textStyleWithFontFamilyFix(typography.titleMedium) textAlign(TextAlign.Center) }, modifier = Modifier.fillMaxWidth(), ) Spacer(Modifier.height(16.dp)) - JetsnackText( + Text( text = stringResource(R.string.grab_beverage), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodyMedium) + 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 66df1f0820..5576b38f06 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,7 +45,6 @@ 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.runtime.Composable import androidx.compose.runtime.getValue @@ -55,7 +54,6 @@ 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 @@ -71,20 +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.JetsnackText +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.jetsnackTextStyle +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.currentJetsnackTheme +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 @@ -117,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, @@ -160,11 +158,11 @@ private fun CartContent( WindowInsets.statusBars.add(WindowInsets(top = 56.dp)), ), ) - JetsnackText( + Text( text = stringResource(R.string.cart_order_header, snackCountFormattedString), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) - contentColor(currentJetsnackTheme.colors.brand) + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.brand) }, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -269,16 +267,16 @@ 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) { - JetsnackText( + Text( text = stringResource(id = R.string.remove_item), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) - contentColor(currentJetsnackTheme.colors.uiBackground) + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.uiBackground) textAlign(TextAlign.Center) }, modifier = Modifier @@ -326,11 +324,11 @@ fun CartItem( .padding(start = 16.dp), ) { Row(modifier = Modifier.fillMaxWidth()) { - JetsnackText( + Text( text = snack.name, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) - contentColor(currentJetsnackTheme.colors.textSecondary) + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textSecondary) }, modifier = Modifier .weight(1f) @@ -347,11 +345,11 @@ fun CartItem( ) } } - JetsnackText( + Text( text = snack.tagline, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) - contentColor(currentJetsnackTheme.colors.textHelp) + textStyleWithFontFamilyFix(typography.bodyLarge) + contentColor(colors.textHelp) }, modifier = Modifier.padding(end = 16.dp), ) @@ -359,11 +357,11 @@ fun CartItem( Row( modifier = Modifier.fillMaxWidth(), ) { - JetsnackText( + Text( text = formatPrice(snack.price), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) - contentColor(currentJetsnackTheme.colors.textPrimary) + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textPrimary) }, modifier = Modifier .weight(1f) @@ -386,11 +384,11 @@ fun CartItem( @Composable fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifier) { Column(modifier) { - JetsnackText( + Text( text = stringResource(R.string.cart_summary_header), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) - contentColor(currentJetsnackTheme.colors.brand) + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.brand) }, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -400,39 +398,39 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi .wrapContentHeight(), ) Row(modifier = Modifier.padding(horizontal = 24.dp)) { - JetsnackText( + Text( text = stringResource(R.string.cart_subtotal_label), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) + textStyleWithFontFamilyFix(typography.bodyLarge) }, modifier = Modifier .weight(1f) .wrapContentWidth(Alignment.Start) .alignBy(LastBaseline), ) - JetsnackText( + Text( text = formatPrice(subtotal), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) + textStyleWithFontFamilyFix(typography.bodyLarge) }, modifier = Modifier.alignBy(LastBaseline), ) } Row(modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) { - JetsnackText( + Text( text = stringResource(R.string.cart_shipping_label), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) + textStyleWithFontFamilyFix(typography.bodyLarge) }, modifier = Modifier .weight(1f) .wrapContentWidth(Alignment.Start) .alignBy(LastBaseline), ) - JetsnackText( + Text( text = formatPrice(shippingCosts), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) + textStyleWithFontFamilyFix(typography.bodyLarge) }, modifier = Modifier.alignBy(LastBaseline), ) @@ -440,10 +438,10 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi Spacer(modifier = Modifier.height(8.dp)) JetsnackDivider() Row(modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) { - JetsnackText( + Text( text = stringResource(R.string.cart_total_label), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) + textStyleWithFontFamilyFix(typography.bodyLarge) }, modifier = Modifier .weight(1f) @@ -451,10 +449,10 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi .wrapContentWidth(Alignment.End) .alignBy(LastBaseline), ) - JetsnackText( + Text( text = formatPrice(subtotal + shippingCosts), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) + textStyleWithFontFamilyFix(typography.titleMedium) }, modifier = Modifier.alignBy(LastBaseline), ) @@ -474,7 +472,7 @@ private fun CheckoutBar(modifier: Modifier = Modifier) { JetsnackDivider() Row { Spacer(Modifier.weight(1f)) - JetsnackButton( + Button( onClick = { /* todo */ }, style = { shape(RectangleShape) @@ -483,7 +481,7 @@ private fun CheckoutBar(modifier: Modifier = Modifier) { .padding(horizontal = 12.dp, vertical = 8.dp) .weight(1f), ) { - JetsnackText( + Text( text = stringResource(id = R.string.cart_checkout), modifier = Modifier.fillMaxWidth(), style = { 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 5238998a94..f17b2eaeed 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 @@ -43,12 +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.JetsnackText +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.jetsnackTextStyle +import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix import com.example.jetsnack.ui.theme.JetsnackTheme -import com.example.jetsnack.ui.theme.currentJetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.typography import kotlin.math.max @Composable @@ -64,11 +65,11 @@ fun SearchCategories(categories: List) { @Composable private fun SearchCategoryCollection(collection: SearchCategoryCollection, index: Int, modifier: Modifier = Modifier) { Column(modifier) { - JetsnackText( + Text( text = collection.name, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) - contentColor(currentJetsnackTheme.colors.textPrimary) + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.textPrimary) }, modifier = Modifier .heightIn(min = 56.dp) @@ -106,11 +107,11 @@ private fun SearchCategory(category: SearchCategory, gradient: List, modi .background(Brush.horizontalGradient(gradient)) .clickable { /* todo */ }, content = { - JetsnackText( + Text( text = category.name, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) - contentColor(currentJetsnackTheme.colors.textSecondary) + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textSecondary) }, modifier = Modifier .padding(4.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 852655b670..14057014f9 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 @@ -45,24 +44,25 @@ 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.JetsnackText +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.jetsnackTextStyle +import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix import com.example.jetsnack.ui.theme.JetsnackTheme -import com.example.jetsnack.ui.theme.currentJetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.typography import com.example.jetsnack.ui.utils.formatPrice @Composable fun SearchResults(searchResults: List, onSnackClick: (Long, String) -> Unit) { Column { - JetsnackText( + Text( text = stringResource(R.string.search_count, searchResults.size), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) - contentColor(currentJetsnackTheme.colors.textPrimary) + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.textPrimary) }, modifier = Modifier.padding(horizontal = 24.dp, vertical = 4.dp), ) @@ -101,30 +101,30 @@ private fun SearchResult(snack: Snack, onSnackClick: (Long, String) -> Unit, sho .weight(1f) .padding(start = 16.dp, end = 16.dp), ) { - JetsnackText( + Text( text = snack.name, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) - contentColor(currentJetsnackTheme.colors.textSecondary) + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textSecondary) }, ) - JetsnackText( + Text( text = snack.tagline, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) - contentColor(currentJetsnackTheme.colors.textHelp) + textStyleWithFontFamilyFix(typography.bodyLarge) + contentColor(colors.textHelp) }, ) Spacer(Modifier.height(8.dp)) - JetsnackText( + Text( text = formatPrice(snack.price), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) - contentColor(currentJetsnackTheme.colors.textPrimary) + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(colors.textPrimary) }, ) } - JetsnackButton( + Button( onClick = { /* todo */ }, style = { shape(CircleShape) @@ -155,19 +155,19 @@ fun NoResults(query: String, modifier: Modifier = Modifier) { contentDescription = null, ) Spacer(Modifier.height(24.dp)) - JetsnackText( + Text( text = stringResource(R.string.search_no_matches, query), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) + textStyleWithFontFamilyFix(typography.titleMedium) textAlign(TextAlign.Center) }, modifier = Modifier.fillMaxWidth(), ) Spacer(Modifier.height(16.dp)) - JetsnackText( + Text( text = stringResource(R.string.search_no_matches_retry), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodyMedium) + textStyleWithFontFamilyFix(typography.bodyMedium) textAlign(TextAlign.Center) }, modifier = Modifier.fillMaxWidth(), @@ -181,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 ff24a92711..e284e7951b 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 @@ -57,14 +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.JetsnackText +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.currentJetsnackTheme +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,11 +170,11 @@ private fun SearchBar( searching: Boolean, modifier: Modifier = Modifier, ) { - JetsnackSurface( + Surface( style = { - background(currentJetsnackTheme.colors.uiFloated) - contentColor(currentJetsnackTheme.colors.textSecondary) - shape(currentJetsnackTheme.shapes.small) + background(colors.uiFloated) + contentColor(colors.textSecondary) + shape(shapes.small) }, modifier = modifier .fillMaxWidth() @@ -239,10 +240,10 @@ private fun SearchHint() { contentDescription = stringResource(R.string.label_search), ) Spacer(Modifier.width(8.dp)) - JetsnackText( + Text( text = stringResource(R.string.search_jetsnack), style = { - contentColor(currentJetsnackTheme.colors.textHelp) + contentColor(colors.textHelp) }, ) } @@ -254,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 40d122e71a..8ebe6ba34b 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 @@ -33,11 +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.JetsnackText -import com.example.jetsnack.ui.components.jetsnackTextStyle +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.currentJetsnackTheme +import com.example.jetsnack.ui.theme.colors +import com.example.jetsnack.ui.theme.typography @Composable fun SearchSuggestions(suggestions: List, onSuggestionSelect: (String) -> Unit) { @@ -62,11 +63,11 @@ fun SearchSuggestions(suggestions: List, onSuggestionSele @Composable private fun SuggestionHeader(name: String, modifier: Modifier = Modifier) { - JetsnackText( + Text( text = name, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) - contentColor(currentJetsnackTheme.colors.textPrimary) + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.textPrimary) }, modifier = modifier .heightIn(min = 56.dp) @@ -77,10 +78,10 @@ private fun SuggestionHeader(name: String, modifier: Modifier = Modifier) { @Composable private fun Suggestion(suggestion: String, onSuggestionSelect: (String) -> Unit, modifier: Modifier = Modifier) { - JetsnackText( + Text( text = suggestion, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleMedium) + textStyleWithFontFamilyFix(typography.titleMedium) }, modifier = modifier .heightIn(min = 48.dp) @@ -96,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 a6c35c6f65..ec555a7ab7 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 @@ -65,7 +65,6 @@ 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.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.key @@ -103,18 +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.JetsnackText +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.jetsnackTextStyle +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.currentJetsnackTheme +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 @@ -299,29 +299,29 @@ private fun Body(related: List, scroll: ScrollState) { ) { Spacer(Modifier.height(GradientScroll)) Spacer(Modifier.height(ImageOverlap)) - JetsnackSurface( + Surface( Modifier .fillMaxWidth() .padding(top = 16.dp), ) { Column { Spacer(Modifier.height(TitleHeight)) - JetsnackText( + Text( text = stringResource(R.string.detail_header), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.headlineMedium) - contentColor(currentJetsnackTheme.colors.textHelp) + textStyleWithFontFamilyFix(typography.headlineMedium) + contentColor(colors.textHelp) contentPaddingHorizontal(24.dp) }, ) Spacer(Modifier.height(16.dp)) var seeMore by remember { mutableStateOf(true) } with(sharedTransitionScope) { - JetsnackText( + Text( text = stringResource(R.string.detail_placeholder), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) - contentColor(currentJetsnackTheme.colors.textHelp) + textStyleWithFontFamilyFix(typography.bodyLarge) + contentColor(colors.textHelp) contentPaddingHorizontal(24.dp) }, maxLines = if (seeMore) 5 else Int.MAX_VALUE, @@ -335,11 +335,11 @@ private fun Body(related: List, scroll: ScrollState) { stringResource(id = R.string.see_less) } - JetsnackText( + Text( text = textButton, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.labelLarge) - contentColor(currentJetsnackTheme.colors.textLink) + textStyleWithFontFamilyFix(typography.labelLarge) + contentColor(colors.textLink) textAlign(TextAlign.Center) contentPaddingTop(15.dp) fillWidth() @@ -353,20 +353,20 @@ private fun Body(related: List, scroll: ScrollState) { ) Spacer(Modifier.height(40.dp)) - JetsnackText( + Text( text = stringResource(R.string.ingredients), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.labelSmall) - contentColor(currentJetsnackTheme.colors.textHelp) + textStyleWithFontFamilyFix(typography.labelSmall) + contentColor(colors.textHelp) contentPaddingHorizontal(24.dp) }, ) Spacer(Modifier.height(4.dp)) - JetsnackText( + Text( text = stringResource(R.string.ingredients_list), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.bodyLarge) - contentColor(currentJetsnackTheme.colors.textHelp) + textStyleWithFontFamilyFix(typography.bodyLarge) + contentColor(colors.textHelp) contentPaddingHorizontal(24.dp) }, ) @@ -421,11 +421,11 @@ private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { .background(JetsnackTheme.colors.uiBackground), ) { Spacer(Modifier.height(16.dp)) - JetsnackText( + Text( text = snack.name, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.headlineMedium) - contentColor(currentJetsnackTheme.colors.textSecondary) + textStyleWithFontFamilyFix(typography.headlineMedium) + contentColor(colors.textSecondary) fontStyle(FontStyle.Italic) }, modifier = HzPadding @@ -442,12 +442,12 @@ private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { ) .wrapContentWidth(), ) - JetsnackText( + Text( text = snack.tagline, style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleSmall) + textStyleWithFontFamilyFix(typography.titleSmall) fontStyle(FontStyle.Italic) - contentColor(currentJetsnackTheme.colors.textHelp) + contentColor(colors.textHelp) fontSize(20.sp) }, modifier = HzPadding @@ -466,11 +466,11 @@ private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { ) Spacer(Modifier.height(4.dp)) with(animatedVisibilityScope) { - JetsnackText( + Text( text = formatPrice(snack.price), style = { - jetsnackTextStyle(currentJetsnackTheme.typography.titleLarge) - contentColor(currentJetsnackTheme.colors.textPrimary) + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.textPrimary) }, modifier = HzPadding .animateEnterExit( @@ -572,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( @@ -601,11 +601,11 @@ private fun CartBottomBar(modifier: Modifier = Modifier) { increaseItemCount = { updateCount(count + 1) }, ) Spacer(Modifier.width(16.dp)) - JetsnackButton( + Button( onClick = { /* todo */ }, modifier = Modifier.weight(1f), ) { - JetsnackText( + Text( text = stringResource(R.string.add_to_cart), modifier = Modifier.fillMaxWidth(), style = { 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 index 9b6012b704..acc31f6237 100644 --- 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 @@ -22,12 +22,10 @@ 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.StyleScope 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.ButtonDefaults import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.Brush @@ -35,48 +33,48 @@ 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.jetsnackTextStyle -import com.example.jetsnack.ui.theme.JetsnackTheme.Companion.LocalJetsnackTheme +import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix @Immutable data class AppStyles( val buttonStyle: Style = Style { shape(RoundedCornerShape(percent = 50)) - background(Brush.linearGradient(currentJetsnackTheme.colors.interactivePrimary)) - contentColor(currentJetsnackTheme.colors.textInteractive) - contentPadding(ButtonDefaults.ContentPadding.calculateTopPadding()) // todo file issue to support padding values - jetsnackTextStyle(currentJetsnackTheme.typography.labelLarge) + background(Brush.linearGradient(colors.interactivePrimary)) + contentColor(colors.textInteractive) + contentPaddingVertical(8.dp) + contentPaddingHorizontal(24.dp) + textStyleWithFontFamilyFix(typography.labelLarge) disabled { animate { - background(Brush.linearGradient(currentJetsnackTheme.colors.interactiveSecondary)) - contentColor(currentJetsnackTheme.colors.textHelp) + background(Brush.linearGradient(colors.interactiveSecondary)) + contentColor(colors.textHelp) } } }, val cardStyle: Style = Style { - shape(currentJetsnackTheme.shapes.medium) - background(currentJetsnackTheme.colors.uiBackground) - contentColor(currentJetsnackTheme.colors.textPrimary) + shape(shapes.medium) + background(colors.uiBackground) + contentColor(colors.textPrimary) /* todo elevation elevation: Dp = 4.dp,*/ }, val dividerStyle: Style = Style { - background(currentJetsnackTheme.colors.uiBorder.copy(alpha = 0.12f)) + background(colors.uiBorder.copy(alpha = 0.12f)) height(1.dp) fillWidth() }, val gradientIconButtonStyle: Style = Style { shape(CircleShape) clip(true) - border(2.dp, Brush.linearGradient(currentJetsnackTheme.colors.interactiveSecondary)) - background(currentJetsnackTheme.colors.uiBackground) + 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 = currentJetsnackTheme.colors.interactiveSecondary, + colors = colors.interactiveSecondary, startX = 0f, endX = 200f, tileMode = TileMode.Mirror, @@ -86,28 +84,26 @@ data class AppStyles( } }, val filterChipStyle: Style = Style { - shape(currentJetsnackTheme.shapes.small) - background(currentJetsnackTheme.colors.uiBackground) - contentColor(currentJetsnackTheme.colors.textSecondary) - border(1.dp, Brush.linearGradient(currentJetsnackTheme.colors.interactiveSecondary)) + shape(shapes.small) + background(colors.uiBackground) + contentColor(colors.textSecondary) + border(1.dp, Brush.linearGradient(colors.interactiveSecondary)) // todo elevation = 2.dp, selected { animate { - background(currentJetsnackTheme.colors.brandSecondary) + background(colors.brandSecondary) contentColor(Color.Black) border(1.dp, Color.Transparent) } } }, val defaultTextStyle: Style = Style { - jetsnackTextStyle(LocalTextStyle.currentValue) + textStyleWithFontFamilyFix(LocalTextStyle.currentValue) }, val surfaceStyle: Style = Style { shape(RectangleShape) - background(currentJetsnackTheme.colors.uiBackground) - contentColor(currentJetsnackTheme.colors.textSecondary) + background(colors.uiBackground) + contentColor(colors.textSecondary) clip(true) }, ) -val StyleScope.currentJetsnackTheme: JetsnackTheme - get() = LocalJetsnackTheme.currentValue 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 4b19ef0080..16af20c9a1 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 @@ -20,6 +20,7 @@ package com.example.jetsnack.ui.theme import androidx.compose.foundation.isSystemInDarkTheme 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 @@ -34,10 +35,9 @@ import androidx.compose.runtime.staticCompositionLocalOf class JetsnackTheme( val colors: JetsnackColors = LightColorPalette, val typography: Typography = Typography, + val shapes: Shapes = Shapes, val appStyles: AppStyles = AppStyles(), ) { - val shapes: Shapes = _shapes - companion object { val colors: JetsnackColors @Composable @ReadOnlyComposable @@ -57,11 +57,18 @@ class JetsnackTheme( val LocalJetsnackTheme: ProvidableCompositionLocal get() = LocalJetsnackThemeInstance - - private val _shapes = Shapes } } +val StyleScope.colors: JetsnackColors + get() = JetsnackTheme.LocalJetsnackTheme.currentValue.colors + +val StyleScope.typography: Typography + get() = JetsnackTheme.LocalJetsnackTheme.currentValue.typography + +val StyleScope.shapes: Shapes + get() = JetsnackTheme.LocalJetsnackTheme.currentValue.shapes + internal val LocalJetsnackThemeInstance = staticCompositionLocalOf { JetsnackTheme() } @Composable From 2e57f912919f3c14205ab4849881f08362d06f4c Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 16 Mar 2026 20:59:16 +0000 Subject: [PATCH 06/33] Rename `AppStyles` to `Styles` and refactor styling in components - Rename `AppStyles` data class to `Styles` and update all references in `JetsnackTheme`. - Update `JetsnackCard`, `JetsnackDivider`, `JetsnackButton`, and other components to use the renamed `styles` property. - Move border and size styling from `Modifier` to `Style` in `SnackHighlight` cards. - Remove unused `HorizontalDivider` import and clean up styling logic in `Text.kt`. --- .../com/example/jetsnack/ui/components/Button.kt | 2 +- .../java/com/example/jetsnack/ui/components/Card.kt | 2 +- .../com/example/jetsnack/ui/components/Divider.kt | 3 +-- .../com/example/jetsnack/ui/components/Filters.kt | 2 +- .../ui/components/GradientTintedIconButton.kt | 6 +----- .../com/example/jetsnack/ui/components/Snacks.kt | 12 +++--------- .../com/example/jetsnack/ui/components/Surface.kt | 2 +- .../java/com/example/jetsnack/ui/components/Text.kt | 4 ++-- .../java/com/example/jetsnack/ui/theme/Styles.kt | 2 +- .../main/java/com/example/jetsnack/ui/theme/Theme.kt | 8 ++++---- 10 files changed, 16 insertions(+), 27 deletions(-) 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 b5fc023b37..6c25594383 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 @@ -53,7 +53,7 @@ fun Button( it.isEnabled = enabled }) Surface( - style = JetsnackTheme.appStyles.buttonStyle then style, + style = JetsnackTheme.styles.buttonStyle then style, styleState = styleState, modifier = modifier .clickable( 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 dc8c4041e4..5cc626f2ba 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 @@ -33,7 +33,7 @@ import com.example.jetsnack.ui.theme.JetsnackTheme fun JetsnackCard(modifier: Modifier = Modifier, style: Style = Style, content: @Composable () -> Unit) { Surface( modifier = modifier, - style = JetsnackTheme.appStyles.cardStyle then style, + style = JetsnackTheme.styles.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 8a14dcb806..a135fa9740 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 @@ -24,7 +24,6 @@ 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 @@ -35,7 +34,7 @@ import com.example.jetsnack.ui.theme.JetsnackTheme.Companion.LocalJetsnackTheme @Composable fun JetsnackDivider(modifier: Modifier = Modifier, style: Style = Style) { - Box(modifier = modifier.styleable(null, LocalJetsnackTheme.current.appStyles.dividerStyle, style)) + Box(modifier = modifier.styleable(null, LocalJetsnackTheme.current.styles.dividerStyle, style)) } @Preview("default", showBackground = true) 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 58dc08f5a8..64ad722db6 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 @@ -128,7 +128,7 @@ fun FilterChip(filter: Filter, modifier: Modifier = Modifier, style: Style = Sty interactionSource = interactionSource, indication = null, ), - style = JetsnackTheme.appStyles.filterChipStyle then style, + style = JetsnackTheme.styles.filterChipStyle then style, styleState = styleState, ) { val innerBackgroundStyle = Style { 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 0b73d55bde..5b958a60f4 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 @@ -23,19 +23,15 @@ import androidx.annotation.DrawableRes 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.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.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color @@ -66,7 +62,7 @@ fun JetsnackGradientTintedIconButton( interactionSource = interactionSource, indication = null, ) - .styleable(styleState, JetsnackTheme.appStyles.gradientIconButtonStyle, style), + .styleable(styleState, JetsnackTheme.styles.gradientIconButtonStyle, style), color = Color.Transparent, ) { val blendMode = if (JetsnackTheme.colors.isDark) BlendMode.Darken else BlendMode.Plus 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 b77f6935fb..29671c719c 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 @@ -291,6 +291,8 @@ private fun HighlightSnackItem( JetsnackCard( style = Style { shape(RoundedCornerShape(roundedCornerAnimation)) + border(1.dp, colors.uiBorder.copy(alpha = 0.12f)) + size(width = HighlightCardWidth, height = 250.dp) }, modifier = modifier .padding(bottom = 16.dp) @@ -311,15 +313,6 @@ private fun HighlightSnackItem( ), enter = fadeIn(), exit = fadeOut(), - ) - .size( - width = HighlightCardWidth, - height = 250.dp, - ) - .border( - 1.dp, - JetsnackTheme.colors.uiBorder.copy(alpha = 0.12f), - RoundedCornerShape(roundedCornerAnimation), ), ) { @@ -395,6 +388,7 @@ private fun HighlightSnackItem( } Spacer(modifier = Modifier.height(8.dp)) + Text( text = snack.name, maxLines = 1, 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 fbbfd30f71..6f24605445 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 @@ -42,7 +42,7 @@ fun Surface( ) { Box( modifier = modifier - .styleable(styleState, JetsnackTheme.appStyles.surfaceStyle, style) + .styleable(styleState, JetsnackTheme.styles.surfaceStyle, style) ) { //todo double check CompositionLocalProvider(LocalContentColor provides contentColor, content = content) content() 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 index f0e374b16e..cf1b88e949 100644 --- 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 @@ -33,7 +33,7 @@ import com.example.jetsnack.ui.theme.JetsnackTheme @ExperimentalFoundationStyleApi fun StyleScope.textStyleWithFontFamilyFix(value: TextStyle) { textStyle(value) - value.fontFamily?.let { fontFamily(it) } + //value.fontFamily?.let { fontFamily(it) } } @ExperimentalFoundationStyleApi @@ -51,7 +51,7 @@ fun Text( ) { BasicText( text = text, - modifier = modifier.styleable(null, JetsnackTheme.appStyles.defaultTextStyle, style), + modifier = modifier.styleable(null, JetsnackTheme.styles.defaultTextStyle, style), onTextLayout = onTextLayout, overflow = overflow, softWrap = softWrap, 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 index acc31f6237..f9db5ad8ee 100644 --- 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 @@ -36,7 +36,7 @@ import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix @Immutable -data class AppStyles( +data class Styles( val buttonStyle: Style = Style { shape(RoundedCornerShape(percent = 50)) background(Brush.linearGradient(colors.interactivePrimary)) 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 16af20c9a1..bbf60c7c56 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 @@ -36,7 +36,7 @@ class JetsnackTheme( val colors: JetsnackColors = LightColorPalette, val typography: Typography = Typography, val shapes: Shapes = Shapes, - val appStyles: AppStyles = AppStyles(), + val styles: Styles = Styles(), ) { companion object { val colors: JetsnackColors @@ -51,9 +51,9 @@ class JetsnackTheme( @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.shapes - val appStyles: AppStyles + val styles: Styles @Composable @ReadOnlyComposable - get() = LocalJetsnackTheme.current.appStyles + get() = LocalJetsnackTheme.current.styles val LocalJetsnackTheme: ProvidableCompositionLocal get() = LocalJetsnackThemeInstance @@ -74,7 +74,7 @@ internal val LocalJetsnackThemeInstance = staticCompositionLocalOf { JetsnackThe @Composable fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { val colors = if (darkTheme) DarkColorPalette else LightColorPalette - val theme = JetsnackTheme(colors = colors, appStyles = AppStyles()) + val theme = JetsnackTheme(colors = colors, styles = Styles()) CompositionLocalProvider( JetsnackTheme.LocalJetsnackTheme provides theme, From b733ea8cd4b3f5233ab2fbe2cdd85180400e94f4 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 17 Mar 2026 13:07:07 +0000 Subject: [PATCH 07/33] Update snack components and detail view to use Compose Foundation styling APIs. --- .../example/jetsnack/ui/components/Snacks.kt | 43 ++++++++++--------- .../jetsnack/ui/snackdetail/SnackDetail.kt | 3 ++ .../com/example/jetsnack/ui/theme/Styles.kt | 2 +- 3 files changed, 27 insertions(+), 21 deletions(-) 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 29671c719c..2cd70db035 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 @@ -51,6 +51,7 @@ 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.styleable import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable @@ -58,7 +59,10 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue 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.TileMode import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity @@ -220,7 +224,6 @@ fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String ) { SnackImage( imageRes = snack.imageRes, - elevation = 1.dp, contentDescription = null, modifier = Modifier .size(120.dp) @@ -291,7 +294,8 @@ private fun HighlightSnackItem( JetsnackCard( style = Style { shape(RoundedCornerShape(roundedCornerAnimation)) - border(1.dp, colors.uiBorder.copy(alpha = 0.12f)) + clip(true) + border(1.dp, colors.uiBorder) size(width = HighlightCardWidth, height = 250.dp) }, modifier = modifier @@ -313,8 +317,7 @@ private fun HighlightSnackItem( ), enter = fadeIn(), exit = fadeOut(), - ), - + ) ) { Column( modifier = Modifier @@ -332,6 +335,20 @@ private fun HighlightSnackItem( .height(160.dp) .fillMaxWidth(), ) { + // todo investigate this is not clipping properly to the container + val spannedBackgroundGradient = Style { + val left = index * cardWidthWithPaddingPx + val gradientOffset = left - (scrollProvider() / 3f) + + val brush = + Brush.horizontalGradient( + colors = gradient, + startX = -gradientOffset, + endX = (6 * cardWidthWithPaddingPx) - gradientOffset, + tileMode = TileMode.Mirror, + ) + background(brush) + } Box( modifier = Modifier .sharedBounds( @@ -348,21 +365,9 @@ private fun HighlightSnackItem( exit = fadeOut(nonSpatialExpressiveSpring()), resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds(), ) + .styleable(null, spannedBackgroundGradient) .height(100.dp) .fillMaxWidth() - .offsetGradientBackground( - colors = gradient, - width = { - // The Cards show a gradient which spans 6 cards and - // scrolls with parallax. - 6 * cardWidthWithPaddingPx - }, - offset = { - val left = index * cardWidthWithPaddingPx - val gradientOffset = left - (scrollProvider() / 3f) - gradientOffset - }, - ), ) SnackImage( @@ -458,13 +463,11 @@ fun SnackImage( @DrawableRes imageRes: Int, contentDescription: String?, - modifier: Modifier = Modifier, - elevation: Dp = 0.dp, + modifier: Modifier = Modifier ) { Surface( style = { shape(CircleShape) - // todo elevation }, modifier = modifier, ) { 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 ec555a7ab7..7ae75aa3e7 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 @@ -604,6 +604,9 @@ private fun CartBottomBar(modifier: Modifier = Modifier) { Button( onClick = { /* todo */ }, modifier = Modifier.weight(1f), + style = { + externalPadding(4.dp) + } ) { Text( text = stringResource(R.string.add_to_cart), 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 index f9db5ad8ee..5fa46b83ea 100644 --- 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 @@ -38,7 +38,7 @@ import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix @Immutable data class Styles( val buttonStyle: Style = Style { - shape(RoundedCornerShape(percent = 50)) + shape(shapes.small) background(Brush.linearGradient(colors.interactivePrimary)) contentColor(colors.textInteractive) contentPaddingVertical(8.dp) From d116df3b4d1bf89b0adcc319e1a15d1cdb80e1d3 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 17 Mar 2026 13:09:21 +0000 Subject: [PATCH 08/33] Enable font family application in textStyleWithFontFamilyFix --- .../src/main/java/com/example/jetsnack/ui/components/Text.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index cf1b88e949..b37c4208d0 100644 --- 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 @@ -33,7 +33,7 @@ import com.example.jetsnack.ui.theme.JetsnackTheme @ExperimentalFoundationStyleApi fun StyleScope.textStyleWithFontFamilyFix(value: TextStyle) { textStyle(value) - //value.fontFamily?.let { fontFamily(it) } + value.fontFamily?.let { fontFamily(it) } } @ExperimentalFoundationStyleApi From 252b8f7a24922badaa8b3bc93c022e52abc342ee Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Wed, 18 Mar 2026 13:17:39 +0000 Subject: [PATCH 09/33] Simplify button definition. --- .../example/jetsnack/ui/components/Button.kt | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) 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 6c25594383..97910aa870 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 @@ -22,12 +22,14 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.foundation.style.Style import androidx.compose.foundation.style.rememberUpdatedStyleState +import androidx.compose.foundation.style.styleable import androidx.compose.foundation.style.then import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ripple @@ -37,6 +39,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview import com.example.jetsnack.ui.theme.JetsnackTheme @@ -49,32 +53,24 @@ fun Button( interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, content: @Composable RowScope.() -> Unit, ) { - val styleState = rememberUpdatedStyleState(interactionSource, { + val styleState = rememberUpdatedStyleState(interactionSource) { it.isEnabled = enabled - }) - Surface( - style = JetsnackTheme.styles.buttonStyle then style, - styleState = styleState, + } + Row( modifier = modifier + .semantics(properties = { + role = Role.Button + }) .clickable( - onClick = onClick, enabled = enabled, - role = Role.Button, + onClick = onClick, interactionSource = interactionSource, - indication = ripple() //TODO This ripple doesn't know the shape of the button. - ), - ) { - Row( - Modifier - .defaultMinSize( - minWidth = ButtonDefaults.MinWidth, - minHeight = ButtonDefaults.MinHeight, - ), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - content = content, - ) - } + indication = null, + ) + .styleable(styleState, JetsnackTheme.styles.buttonStyle, style), + content = content, + verticalAlignment = Alignment.CenterVertically + ) } @Preview("default", "round") From 8ca8c344cb5df22fce7a36438c1e6107efc7f339 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Wed, 18 Mar 2026 13:20:20 +0000 Subject: [PATCH 10/33] Simplify button definition. --- .../example/jetsnack/ui/components/Button.kt | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) 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 97910aa870..2c42a1f9bb 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 @@ -21,18 +21,12 @@ package com.example.jetsnack.ui.components import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.foundation.style.Style import androidx.compose.foundation.style.rememberUpdatedStyleState import androidx.compose.foundation.style.styleable -import androidx.compose.foundation.style.then -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -58,9 +52,11 @@ fun Button( } Row( modifier = modifier - .semantics(properties = { - role = Role.Button - }) + .semantics( + properties = { + role = Role.Button + }, + ) .clickable( enabled = enabled, onClick = onClick, @@ -69,7 +65,7 @@ fun Button( ) .styleable(styleState, JetsnackTheme.styles.buttonStyle, style), content = content, - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) } @@ -92,7 +88,8 @@ private fun ButtonPreview() { private fun RectangleButtonPreview() { JetsnackTheme { Button( - onClick = {}, style = { + onClick = {}, + style = { shape(RectangleShape) }, ) { From bf00df62c535cede46153564d4d1a7505e33e4b3 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Wed, 18 Mar 2026 17:43:56 +0000 Subject: [PATCH 11/33] Add minSize to the Button --- .../example/jetsnack/ui/components/Button.kt | 3 +- .../com/example/jetsnack/ui/theme/Styles.kt | 31 ++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) 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 2c42a1f9bb..ce8a9a0e55 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 @@ -27,6 +27,7 @@ import androidx.compose.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.foundation.style.Style import androidx.compose.foundation.style.rememberUpdatedStyleState import androidx.compose.foundation.style.styleable +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -61,7 +62,7 @@ fun Button( enabled = enabled, onClick = onClick, interactionSource = interactionSource, - indication = null, + indication = ripple(), ) .styleable(styleState, JetsnackTheme.styles.buttonStyle, style), content = content, 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 index 5fa46b83ea..04d3c190dc 100644 --- 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 @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(ExperimentalFoundationStyleApi::class) +@file:OptIn(ExperimentalFoundationStyleApi::class, ExperimentalMediaQueryApi::class) package com.example.jetsnack.ui.theme @@ -22,19 +22,47 @@ 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.StyleScope import androidx.compose.foundation.style.disabled import androidx.compose.foundation.style.fillWidth +import androidx.compose.foundation.style.hovered import androidx.compose.foundation.style.pressed import androidx.compose.foundation.style.selected +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Immutable +import androidx.compose.ui.ExperimentalMediaQueryApi +import androidx.compose.ui.LocalUiMediaScope +import androidx.compose.ui.UiMediaScope +import androidx.compose.ui.UiMediaScope.ViewingDistance 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.mediaQuery +import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix +fun StyleScope.adaptiveFontSize(fontSize: TextUnit) { + var scaleFactor = when (LocalUiMediaScope.currentValue.viewingDistance) { + ViewingDistance.Near -> 1f + ViewingDistance.Medium -> 1.72f + ViewingDistance.Far -> 1.5f + else -> 1f + } + scaleFactor = when (LocalUiMediaScope.currentValue.pointerPrecision) { + UiMediaScope.PointerPrecision.Coarse -> scaleFactor * 1f + UiMediaScope.PointerPrecision.Blunt -> scaleFactor * 0.66f + UiMediaScope.PointerPrecision.Fine -> scaleFactor * 1f + UiMediaScope.PointerPrecision.None -> scaleFactor + else -> { + scaleFactor + } + } + fontSize(fontSize * scaleFactor) +} + @Immutable data class Styles( val buttonStyle: Style = Style { @@ -43,6 +71,7 @@ data class Styles( contentColor(colors.textInteractive) contentPaddingVertical(8.dp) contentPaddingHorizontal(24.dp) + minSize(58.dp, 48.dp) textStyleWithFontFamilyFix(typography.labelLarge) disabled { animate { From 8d4514e0f224bb1260d5e7c511d5d6fef6961243 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 23 Mar 2026 12:20:47 +0000 Subject: [PATCH 12/33] Add PreviewWrapper.kt --- .../example/jetsnack/ui/components/Button.kt | 22 +++++ .../com/example/jetsnack/ui/theme/Styles.kt | 9 +- .../jetsnack/ui/utils/CommonPreviewWrapper.kt | 24 +++++ .../jetsnack/ui/utils/PreviewWrapper.kt | 97 +++++++++++++++++++ Jetsnack/gradle/libs.versions.toml | 4 +- 5 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/CommonPreviewWrapper.kt create mode 100644 Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt 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 ce8a9a0e55..5f8e8a318d 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 @@ -37,7 +37,10 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewWrapperProvider import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.utils.DesktopPreviewWrapper +import com.example.jetsnack.ui.utils.PhoneUiMediaScopeWrapper @Composable fun Button( @@ -70,6 +73,8 @@ fun Button( ) } +@PreviewWrapperProvider(PhoneUiMediaScopeWrapper::class) +@Preview @Preview("default", "round") @Preview("dark theme", "round", uiMode = UI_MODE_NIGHT_YES) @Preview("large font", "round", fontScale = 2f) @@ -82,6 +87,7 @@ private fun ButtonPreview() { } } +@PreviewWrapperProvider(PhoneUiMediaScopeWrapper::class) @Preview("default", "rectangle") @Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) @Preview("large font", "rectangle", fontScale = 2f) @@ -98,3 +104,19 @@ private fun RectangleButtonPreview() { } } } + +@PreviewWrapperProvider(DesktopPreviewWrapper::class) +@Preview("default", "rectangle") +@Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) +@Composable +private fun ButtonDesktopPreview() { + JetsnackTheme { + Button( + onClick = {}, + ) { + Text(text = "Demo") + } + } +} + + 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 index 04d3c190dc..96264b1fd9 100644 --- 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 @@ -69,8 +69,13 @@ data class Styles( shape(shapes.small) background(Brush.linearGradient(colors.interactivePrimary)) contentColor(colors.textInteractive) - contentPaddingVertical(8.dp) - contentPaddingHorizontal(24.dp) + if (mediaQuery { keyboardKind == UiMediaScope.KeyboardKind.Physical }) { + contentPaddingVertical(4.dp) + contentPaddingHorizontal(8.dp) + } else { + contentPaddingVertical(8.dp) + contentPaddingHorizontal(24.dp) + } minSize(58.dp, 48.dp) textStyleWithFontFamilyFix(typography.labelLarge) disabled { diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/CommonPreviewWrapper.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/CommonPreviewWrapper.kt new file mode 100644 index 0000000000..f9b6b14e84 --- /dev/null +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/CommonPreviewWrapper.kt @@ -0,0 +1,24 @@ +package com.example.jetsnack.ui.utils + +import androidx.compose.runtime.Composable +import androidx.compose.ui.layout.LookaheadScope +import androidx.compose.ui.tooling.preview.PreviewWrapper +import com.example.jetsnack.ui.theme.JetsnackTheme + +class ThemeWrapper: PreviewWrapper { + @Composable + override fun Wrap(content: @Composable (() -> Unit)) { + JetsnackTheme { + content() + } + } +} +class LookaheadScopeWrapper: PreviewWrapper { + @Composable + override fun Wrap(content: @Composable (() -> Unit)) { + LookaheadScope { + content() + } + } + +} \ No newline at end of file diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt new file mode 100644 index 0000000000..2ebb02b5b5 --- /dev/null +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt @@ -0,0 +1,97 @@ + +@file:OptIn(ExperimentalMediaQueryApi::class) +package com.example.jetsnack.ui.utils + + + +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.ComposeUiFlags +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.ExperimentalMediaQueryApi +import androidx.compose.ui.LocalUiMediaScope +import androidx.compose.ui.UiMediaScope +import androidx.compose.ui.tooling.preview.PreviewWrapper + + +class DesktopPreviewWrapper : PreviewWrapper { + private val themeWrapper = ThemeWrapper() + private val desktopPreviewWrapper = DesktopUiMediaScopeWrapper() + + private val lookaheadScopeWrapper = LookaheadScopeWrapper() + + @Composable + override fun Wrap(content: @Composable () -> Unit) { + // Nest the wrappers: Theme is usually the outermost layer, + // followed by the environment/container wrapper. + themeWrapper.Wrap { + lookaheadScopeWrapper.Wrap { + desktopPreviewWrapper.Wrap { + content() + } + } + } + } +} + +/** + * This class is a workaround for UiMediaQuery not exposing previews just yet. + * + */ +@OptIn(ExperimentalComposeUiApi::class) +class DesktopUiMediaScopeWrapper: PreviewWrapper { + @Composable + override fun Wrap(content: @Composable (() -> Unit)) { + // Step 1: Enable the mediaQuery function + ComposeUiFlags.isMediaQueryIntegrationEnabled = true + BoxWithConstraints { + // Step 2: Define a custom object implementing the UiMediaScope interface. + val uiMediaScope = object : UiMediaScope { + override val keyboardKind: UiMediaScope.KeyboardKind + get() = UiMediaScope.KeyboardKind.Physical + override val windowPosture: UiMediaScope.Posture + get() = UiMediaScope.Posture.Flat + override val windowWidth = maxWidth // faking the width and height for previews + override val windowHeight = maxHeight + override val pointerPrecision = UiMediaScope.PointerPrecision.Fine + override val hasMicrophone = true + override val hasCamera = true + override val viewingDistance = UiMediaScope.ViewingDistance.Near + } + + // Step 3: Set the object to the LocalUiMediaScope. + CompositionLocalProvider(LocalUiMediaScope provides uiMediaScope) { + content() + } + } + } +} +@OptIn(ExperimentalComposeUiApi::class) +class PhoneUiMediaScopeWrapper: PreviewWrapper { + @Composable + override fun Wrap(content: @Composable (() -> Unit)) { + // Step 1: Enable the mediaQuery function + ComposeUiFlags.isMediaQueryIntegrationEnabled = true + BoxWithConstraints { + // Step 2: Define a custom object implementing the UiMediaScope interface. + val uiMediaScope = object : UiMediaScope { + override val keyboardKind: UiMediaScope.KeyboardKind + get() = UiMediaScope.KeyboardKind.Virtual + override val windowPosture: UiMediaScope.Posture + get() = UiMediaScope.Posture.Flat + override val windowWidth = maxWidth // faking the width and height for previews + override val windowHeight = maxHeight + override val pointerPrecision = UiMediaScope.PointerPrecision.Blunt + override val hasMicrophone = true + override val hasCamera = true + override val viewingDistance = UiMediaScope.ViewingDistance.Near + } + + // Step 3: Set the object to the LocalUiMediaScope. + CompositionLocalProvider(LocalUiMediaScope provides uiMediaScope) { + content() + } + } + } +} diff --git a/Jetsnack/gradle/libs.versions.toml b/Jetsnack/gradle/libs.versions.toml index c10875b44e..b5d5319724 100644 --- a/Jetsnack/gradle/libs.versions.toml +++ b/Jetsnack/gradle/libs.versions.toml @@ -80,8 +80,8 @@ androidx-compose-ui-test = { module = "androidx.compose.ui:ui-test" } androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" } androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" } androidx-compose-ui-text = { module = "androidx.compose.ui:ui-text" } -androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } -androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } +androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version = "1.11.0-beta01" } +androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version = "1.11.0-beta01" } androidx-compose-ui-util = { module = "androidx.compose.ui:ui-util" } androidx-compose-ui-viewbinding = { module = "androidx.compose.ui:ui-viewbinding" } androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-corektx" } From 180e40b723c5dff2b4910b20e1622ae4062696de Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 23 Mar 2026 15:05:53 +0000 Subject: [PATCH 13/33] Add Button Variants with Loading state. Update typography to Instrument Sans and enhance Button styling This change migrates the app's primary typeface from local Montserrat files to Instrument Sans via the Google Fonts API and introduces a more complex, state-driven styling system for buttons. ### Summary of changes * **Typography**: Replaced local `Montserrat` font family with `Instrument Sans` using the `androidx.compose.ui:ui-text-google-fonts` library. Updated the `Typography` configuration to use the new font family across display, headline, and title styles. * **Button Component**: * Added a `LoadingState` (Loading, Loaded, Error) to the `Button` component. * Updated `buttonStyle` to include sophisticated visuals including elliptical gradients, drop shadows, and inner shadows. * Defined specific animated transitions for `pressed`, `loading`, and `disabled` states within the style definition. * Removed the default ripple indication in favor of custom state visuals. * **Styling Utilities**: Introduced an `ellipticalGradient` helper function to emulate CSS-like radial gradients using `ShaderBrush` and `RadialGradientShader`. * **Theme & Resources**: * Updated `Shapes.small` to use a fixed 28.dp corner radius. * Added required font provider certificates in `font_certs.xml`. * Removed legacy Montserrat `.ttf` assets. * **Dependencies**: Added `androidx.compose.ui:ui-text-google-fonts` to the version catalog and app dependencies. --- Jetsnack/app/build.gradle.kts | 1 + .../example/jetsnack/ui/components/Button.kt | 43 +++++- .../com/example/jetsnack/ui/theme/Shape.kt | 2 +- .../com/example/jetsnack/ui/theme/Styles.kt | 146 ++++++++++++++++-- .../com/example/jetsnack/ui/theme/Type.kt | 43 +++--- .../src/main/res/font/montserrat_light.ttf | Bin 242068 -> 0 bytes .../src/main/res/font/montserrat_medium.ttf | Bin 243180 -> 0 bytes .../src/main/res/font/montserrat_regular.ttf | Bin 245708 -> 0 bytes .../src/main/res/font/montserrat_semibold.ttf | Bin 243816 -> 0 bytes .../src/main/res/values-v23/font_certs.xml | 31 ++++ Jetsnack/gradle/libs.versions.toml | 2 + 11 files changed, 228 insertions(+), 40 deletions(-) delete mode 100755 Jetsnack/app/src/main/res/font/montserrat_light.ttf delete mode 100755 Jetsnack/app/src/main/res/font/montserrat_medium.ttf delete mode 100755 Jetsnack/app/src/main/res/font/montserrat_regular.ttf delete mode 100755 Jetsnack/app/src/main/res/font/montserrat_semibold.ttf create mode 100644 Jetsnack/app/src/main/res/values-v23/font_certs.xml diff --git a/Jetsnack/app/build.gradle.kts b/Jetsnack/app/build.gradle.kts index 2d1d6a086d..6ae96b772d 100644 --- a/Jetsnack/app/build.gradle.kts +++ b/Jetsnack/app/build.gradle.kts @@ -107,6 +107,7 @@ android { } dependencies { + implementation(libs.androidx.compose.ui.text.google.fonts) val composeBom = platform(libs.androidx.compose.bom) implementation(composeBom) androidTestImplementation(composeBom) 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 5f8e8a318d..9f3937496c 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,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(ExperimentalFoundationStyleApi::class) +@file:OptIn(ExperimentalFoundationStyleApi::class, ExperimentalMediaQueryApi::class) package com.example.jetsnack.ui.components @@ -27,10 +27,10 @@ import androidx.compose.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.foundation.style.Style import androidx.compose.foundation.style.rememberUpdatedStyleState import androidx.compose.foundation.style.styleable -import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.semantics.Role @@ -39,6 +39,8 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewWrapperProvider import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.theme.LoadingState +import com.example.jetsnack.ui.theme.loadingState import com.example.jetsnack.ui.utils.DesktopPreviewWrapper import com.example.jetsnack.ui.utils.PhoneUiMediaScopeWrapper @@ -48,11 +50,13 @@ fun Button( modifier: Modifier = Modifier, style: Style = Style, enabled: Boolean = true, + loadingState: LoadingState = LoadingState.Loaded, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, content: @Composable RowScope.() -> Unit, ) { val styleState = rememberUpdatedStyleState(interactionSource) { it.isEnabled = enabled + it.loadingState = loadingState } Row( modifier = modifier @@ -65,7 +69,7 @@ fun Button( enabled = enabled, onClick = onClick, interactionSource = interactionSource, - indication = ripple(), + indication = null, ) .styleable(styleState, JetsnackTheme.styles.buttonStyle, style), content = content, @@ -75,9 +79,6 @@ fun Button( @PreviewWrapperProvider(PhoneUiMediaScopeWrapper::class) @Preview -@Preview("default", "round") -@Preview("dark theme", "round", uiMode = UI_MODE_NIGHT_YES) -@Preview("large font", "round", fontScale = 2f) @Composable private fun ButtonPreview() { JetsnackTheme { @@ -87,6 +88,35 @@ private fun ButtonPreview() { } } +@PreviewWrapperProvider(PhoneUiMediaScopeWrapper::class) +@Preview +@Composable +private fun ButtonPreviewLoading() { + JetsnackTheme { + Button( + onClick = {}, + enabled = true, + loadingState = LoadingState.Loading, + ) { + Text(text = "Demo") + } + } +} + +@PreviewWrapperProvider(PhoneUiMediaScopeWrapper::class) +@Preview +@Composable +private fun ButtonPreviewDisabled() { + JetsnackTheme { + Button( + onClick = {}, + enabled = false, + ) { + Text(text = "Demo") + } + } +} + @PreviewWrapperProvider(PhoneUiMediaScopeWrapper::class) @Preview("default", "rectangle") @Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) @@ -120,3 +150,4 @@ private fun ButtonDesktopPreview() { } + diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Shape.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Shape.kt index b8a05338e4..cfc2d4cd2d 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Shape.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Shape.kt @@ -21,7 +21,7 @@ import androidx.compose.material3.Shapes import androidx.compose.ui.unit.dp val Shapes = Shapes( - small = RoundedCornerShape(percent = 50), + small = RoundedCornerShape(size = 28.dp), medium = RoundedCornerShape(20.dp), large = RoundedCornerShape(0.dp), ) 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 index 96264b1fd9..f4374826f5 100644 --- 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 @@ -19,27 +19,33 @@ 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.MutableStyleState import androidx.compose.foundation.style.Style import androidx.compose.foundation.style.StyleScope +import androidx.compose.foundation.style.StyleStateKey import androidx.compose.foundation.style.disabled import androidx.compose.foundation.style.fillWidth -import androidx.compose.foundation.style.hovered import androidx.compose.foundation.style.pressed import androidx.compose.foundation.style.selected -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Immutable import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.LocalUiMediaScope import androidx.compose.ui.UiMediaScope import androidx.compose.ui.UiMediaScope.ViewingDistance +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Matrix +import androidx.compose.ui.graphics.RadialGradientShader import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shader +import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.TileMode -import androidx.compose.ui.mediaQuery +import androidx.compose.ui.graphics.shadow.Shadow +import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix @@ -63,25 +69,108 @@ fun StyleScope.adaptiveFontSize(fontSize: TextUnit) { fontSize(fontSize * scaleFactor) } +/** + * Creates an elliptical radial gradient brush that emulates the CSS radial-gradient spec. + * + * @param colors The colors to be distributed along the gradient. + * @param stops Optional color stops (0.0 to 1.0). + * @param radiusXPercent The horizontal radius as a percentage of the width (1.0 = 100%). + * @param radiusYPercent The vertical radius as a percentage of the height (1.0 = 100%). + * @param centerXPercent The horizontal center position as a percentage of the width. + * @param centerYPercent The vertical center position as a percentage of the height. + * @param tileMode The tile mode for the gradient. + */ +fun ellipticalGradient( + colors: List, + stops: List? = null, + radiusXPercent: Float, + radiusYPercent: Float, + centerXPercent: Float = 0.5f, + centerYPercent: Float = 0.5f, + tileMode: TileMode = TileMode.Clamp, +): ShaderBrush = object : ShaderBrush() { + override fun createShader(size: Size): Shader { + val rX = size.width * radiusXPercent + val rY = size.height * radiusYPercent + val cX = size.width * centerXPercent + val cY = size.height * centerYPercent + + this.transform = Matrix().apply { + reset() + translate(cX, cY) + scale(rX, rY) + } + + return RadialGradientShader( + colors = colors, + colorStops = stops, + center = Offset.Zero, + radius = 1f, + tileMode = tileMode, + ) + } +} + @Immutable data class Styles( val buttonStyle: Style = Style { shape(shapes.small) - background(Brush.linearGradient(colors.interactivePrimary)) - contentColor(colors.textInteractive) - if (mediaQuery { keyboardKind == UiMediaScope.KeyboardKind.Physical }) { - contentPaddingVertical(4.dp) - contentPaddingHorizontal(8.dp) - } else { - contentPaddingVertical(8.dp) - contentPaddingHorizontal(24.dp) - } + // todo extract colors from theme + val schemePrimary = Color(0xFF9685FF) + val schemeInversePrimary = Color(0xFF9D8EFA) + val schemeTertiary = Color(0xFFFFC8A4) + background( + ellipticalGradient( + colors = listOf(schemePrimary, schemeTertiary), + radiusXPercent = 1.3f, + radiusYPercent = 0.7232f, + centerXPercent = 0.4f, + centerYPercent = 0.55f, + ), + ) + + contentColor(Color(0xff0E0066)) + /* if (mediaQuery { keyboardKind == UiMediaScope.KeyboardKind.Physical }) { + contentPaddingVertical(4.dp) + contentPaddingHorizontal(8.dp) + } else {*/ + contentPaddingVertical(8.dp) + contentPaddingHorizontal(24.dp) + /* }*/ minSize(58.dp, 48.dp) textStyleWithFontFamilyFix(typography.labelLarge) + + dropShadow(Shadow(color = schemePrimary, offset = DpOffset(x = 2.dp, y = 4.dp), radius = 12.dp)) + innerShadow(Shadow(color = schemeInversePrimary, offset = DpOffset(x = (-6).dp, (-4).dp), radius = 4.dp)) + pressed { + animate { + background(Brush.radialGradient(listOf(schemePrimary, schemePrimary))) + dropShadow(Shadow(color = schemePrimary, offset = DpOffset(x = 0.dp, y = 0.dp), radius = 0.dp)) + innerShadow(Shadow(color = schemeInversePrimary, offset = DpOffset(x = (0).dp, (0).dp), radius = 0.dp)) + } + } + loading { + animate { + background( + ellipticalGradient( + colors = listOf(schemeTertiary, schemePrimary), + radiusXPercent = 1.3f, + radiusYPercent = 0.7232f, + centerXPercent = 0.4f, + centerYPercent = 0.55f, + ), + ) + dropShadow(Shadow(color = schemePrimary, offset = DpOffset(x = 0.dp, y = 0.dp), radius = 0.dp)) + innerShadow(Shadow(color = schemeInversePrimary, offset = DpOffset(x = (0).dp, (0).dp), radius = 0.dp)) + } + } disabled { animate { - background(Brush.linearGradient(colors.interactiveSecondary)) - contentColor(colors.textHelp) + background(Color(0xffDDD9D9)) + contentColor(Color(0xFF939090)) + // reset shadow + dropShadow(Shadow(color = Color.Transparent, offset = DpOffset(x = 0.dp, y = 0.dp), radius = 0.dp)) + innerShadow(Shadow(color = Color.Transparent, offset = DpOffset(x = (0).dp, (0).dp), radius = 0.dp)) } } }, @@ -141,3 +230,30 @@ data class Styles( clip(true) }, ) + +enum class LoadingState { + Loading, + Loaded, + Error +} + +val loadingStateKey = StyleStateKey(LoadingState.Loaded) + +// Extension Function on MutableStyleState to query and set the current playState +var MutableStyleState.loadingState + get() = this[loadingStateKey] + set(value) { + this[loadingStateKey] = value + } + +fun StyleScope.loading(value: Style) { + state(loadingStateKey, value, { key, state -> state[key] == LoadingState.Loading }) +} + +fun StyleScope.loaded(value: Style) { + state(loadingStateKey, value, { key, state -> state[key] == LoadingState.Loaded }) +} + +fun StyleScope.error(value: Style) { + state(loadingStateKey, value, { key, state -> state[key] == LoadingState.Error }) +} \ No newline at end of file diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Type.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Type.kt index 9f4045fa18..0cbffbbb7f 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Type.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Type.kt @@ -21,14 +21,21 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.googlefonts.Font +import androidx.compose.ui.text.googlefonts.GoogleFont import androidx.compose.ui.unit.sp import com.example.jetsnack.R -private val Montserrat = FontFamily( - Font(R.font.montserrat_light, FontWeight.Light), - Font(R.font.montserrat_regular, FontWeight.Normal), - Font(R.font.montserrat_medium, FontWeight.Medium), - Font(R.font.montserrat_semibold, FontWeight.SemiBold), +val provider = GoogleFont.Provider( + providerAuthority = "com.google.android.gms.fonts", + providerPackage = "com.google.android.gms", + certificates = R.array.com_google_android_gms_fonts_certs, +) + +val instrumentSansFontName = GoogleFont("Instrument Sans") + +val instrumentSansFontFamily = FontFamily( + Font(googleFont = instrumentSansFontName, fontProvider = provider), ) private val Karla = FontFamily( @@ -38,45 +45,45 @@ private val Karla = FontFamily( val Typography = Typography( displayLarge = TextStyle( - fontFamily = Montserrat, + fontFamily = instrumentSansFontFamily, fontSize = 96.sp, fontWeight = FontWeight.Light, lineHeight = 117.sp, letterSpacing = (-1.5).sp, ), displayMedium = TextStyle( - fontFamily = Montserrat, + fontFamily = instrumentSansFontFamily, fontSize = 60.sp, fontWeight = FontWeight.Light, lineHeight = 73.sp, letterSpacing = (-0.5).sp, ), displaySmall = TextStyle( - fontFamily = Montserrat, + fontFamily = instrumentSansFontFamily, fontSize = 48.sp, fontWeight = FontWeight.Normal, lineHeight = 59.sp, ), headlineMedium = TextStyle( - fontFamily = Montserrat, + fontFamily = instrumentSansFontFamily, fontSize = 30.sp, fontWeight = FontWeight.SemiBold, lineHeight = 37.sp, ), headlineSmall = TextStyle( - fontFamily = Montserrat, + fontFamily = instrumentSansFontFamily, fontSize = 24.sp, fontWeight = FontWeight.SemiBold, lineHeight = 29.sp, ), titleLarge = TextStyle( - fontFamily = Montserrat, + fontFamily = instrumentSansFontFamily, fontSize = 20.sp, fontWeight = FontWeight.SemiBold, lineHeight = 24.sp, ), titleMedium = TextStyle( - fontFamily = Montserrat, + fontFamily = instrumentSansFontFamily, fontSize = 16.sp, fontWeight = FontWeight.SemiBold, lineHeight = 24.sp, @@ -97,18 +104,18 @@ val Typography = Typography( letterSpacing = 0.15.sp, ), bodyMedium = TextStyle( - fontFamily = Montserrat, + fontFamily = instrumentSansFontFamily, fontSize = 14.sp, fontWeight = FontWeight.Medium, lineHeight = 20.sp, letterSpacing = 0.25.sp, ), labelLarge = TextStyle( - fontFamily = Montserrat, + lineHeight = 20.sp, + fontWeight = FontWeight(600), + letterSpacing = 0.1.sp, + fontFamily = instrumentSansFontFamily, fontSize = 14.sp, - fontWeight = FontWeight.SemiBold, - lineHeight = 16.sp, - letterSpacing = 1.25.sp, ), bodySmall = TextStyle( fontFamily = Karla, @@ -118,7 +125,7 @@ val Typography = Typography( letterSpacing = 0.4.sp, ), labelSmall = TextStyle( - fontFamily = Montserrat, + fontFamily = instrumentSansFontFamily, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, lineHeight = 16.sp, diff --git a/Jetsnack/app/src/main/res/font/montserrat_light.ttf b/Jetsnack/app/src/main/res/font/montserrat_light.ttf deleted file mode 100755 index 990857de8e03f4db6c2e1112045dfa37ed2d675d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 242068 zcmb?^349#Im3LS596D@CqmeW^tZQbZktOSz(Pi0|MzSvJ@F82iWm&f63tt#x%w-$Q z6+#FJEXgJi60*Q1A;gf74ZDy4n*_cM$bP>s zyOH9jS5>cGy?XVos#chyC?5Q9C>gz7-91n4`RYc6?|fNN_|e|w%T}(MIy#~7EBX}X z^Y^YC>AdUybv=rD^BzU1Enl{>Hayb)aI3=p2me+!Z{0h7V65cnUHJDaijwk&t%nbl z)U;;xDm?8y{QVEx5A4`G6ukQeg)1i&W%k}3<5wL3JOkrBkKai<_FTKY@V;Xcio)h7 z?4`P$6XV;`7Ct?pu%9i*-}AlC_WQl#M-C)?Cv83cJqY}gedBv4HvIMWpDR55*NUQk|G@sM4z2#zg|{o* z`?#Wvcn=($IPmJg`nVORQt5+y~tSP4H&g36almzfR8~kM*Kjf|l{C?(k*YJ-(E<8_i zcJls{k*X(E_6?@se~7-O`uMZv7y;K5^@XuMsL$&6>y!ARet#Mr4e0x1v3lkn1!ulFjfMt3 zsr=eb!>_HJ`OdT!#2H82nmUX%6{EyB5U_Dzt=^aFPBGv#Psp(@XHo{tRf+YJVc?7Y zA2IOVej9wh2@l%fohH1v<;6J4#J<0jWrz~7?q?j1* zCdN=F%rWz6Ou1*(Z(_`J#jA8hJFv%G$xhcqDoa+FJDE)+F^5APRaCWeAO%}T8RghI zi$p^q*NN1yyC)rH!XcGbL)*JXJ6o;&?nU+)ZHvB_)o+UZS0wkiT0*~->p<++KxdlbQ%aPD(fMgY zyI=+mCwEM^bcMw_2i(l*R7TMVROaTUr{|XB78lJ)_oe5AN`px$zA7arJIfUSbz%A+ zZsv)En;IL+1DWC{;Cy1)&BwKuS@7Vo_9pG#w>NIxy77ixyS8hWn3HW--?wfve|B{B z!r@F7(*D%j-PJq!KyQD4k9JC7O7qku{%QV#QlQjCt8#s*DbRteUtvrc14(@Y(1Pk1 zsn;bcsqO+LzdTs(1XYgm^2Uayrbsx)=PnQUU9Rk`oSaCw+2?Wv*n&sK#va)*c)Yq~ z)rqzD3=iM4_U4h2>YE0f8y}vScz9!dUFpi@A6c{JBgW-KHG5Wed5JS62S-jght>%zRu*oeB`OaDJlC=8y$Df#n^LO<#)hA{z`oXYM{Hub7 z>!vnX;clk^2klP&njBXN@N3mpqTO}Qxdp$O*b}3mJyofV&chyYI8_H7vx1SG11`%( z0Dh{HDmJM*C66|#*qpRM`KVUNek!+RO^vZ3b>8t&!Mi%avrPUmNiUYt3eTGX?la)f zYcYJ1qK8C(GquYx+F_g{DxFX?e#l)5xICfeh!cwG=QqxA{e-#>n%goGI4no9_BfiL z$7*%P9>rYggtF7npfYhnS!mEtC`b!vpcBg8BFsM~PN-OmZ!qJ8l5OZ@k(>@#I54Wx z2_@kvHaP7s2~RWNG=Cw@0xst-cY%d3syoBL7qrsJCHwK);B<0Hc+du?lS{&jZ17*| z@Q^zKxU{rB#xij#Tj!h^+f|@xdYfwSX~s5?q$^|d!wCud*qk4oZj!Eiovu=7<1*lL z$F~KZ_%}&3{Tq7B$5-ID+(}D;m#o{uj4NycmaB2}VN)&Uq_2~eWZFbzwR*)43gG9L z#fip#%J0@*tI@uPEku2iu6-CML6OoBt@mVbMJ?joq40hT?7(I{N~&-SJ>yVtIM^7J z-x4-hUREB+aAJ$HhzB~)Z#@;tEyn`iS6_2t$bW=^qyuF?4KQ_Gbp7lqT zu?^bAc{TdUQ0t!;!qKq$BP*idlb}gxGEf0cmd9TZnv7YrT|AEO@4(4Pgrmv%`xT4@ zUNinJu?DjLO;{^QLt3J?#5=Xa#3@RQqoS>j)9Wr%ZzJ*!e;R+!-4}2{_m!=eog3A3HQwa&oSWG)ieXW zAM9%MBWI?38E{qcMl%5E9FPYngpCf)h$nDeBtmrK|&y99za zN3toT z>QlZUxG&(YDG4W9l~*k^W$5@_fd5G)ohtkB>u@<|(y0<2)ZueL>nwG#gkw)u0M4Ww zF)2q&r$WkWF$&z3?B~Qq@K<4Tg-tJe%%>g|hCf_l*8tz6%!|&Ym6jGLABD2%q=mLx zpdKZ|<1Hf#G$*#oFyU#Pk8dq1E?idI)2(T&aq}i;W39icdc`XCe2td5Yd29qBMBbt zq;B0gJeepEmdVqgAXO=j&Pj%GP9(q(hMeKtB+EQA)063d7G>^i+Gue9S%CFFh@JLy zoAwG@(#F<(4qVsT*o)eW+Rd0NosmzAv385GeEhR=H$l(PSbjyq8NqcLqJTSmm@^R&11mH8jrDCG~mHA4!C&?Bq+tIFMx?72^xI+mL0V zD=Mrs0e8K{-!SQ-@%@U#XZ$`17x+3H-rRiVnEJl1v%YVfjo9&)w%GA{+o@K5k<*z; zeFoS&L>u@%SDlo7Pi?TagC!~36=KZAk`|$>%Gt`Pcl9yfjp<3X6F9TTb4iAaq9A&M zb`0`!RE1*b9B`5|0}CBv&QfM!1vBX&ELE{|q%IkYc-0K1{VGrURk(`HXY(r=U!%F$ zdy`)RcTe*|s3O7PJ~5JCxrV5BO{ppPEv1I^T(T03mL{p32yj4tF*5uok{Y1V37qL6 zdBvD=SSaaAEfA?Rzdy@2%dsvw5TydguBEY*ohlAzU7Dm07I1h-@w!>U zP2CJBlR8+!nGK&TF$qsI;HJ(N@R-iV3Q|8=WEFVRW~*FvyTD`K59ZR5ggv^LBvtr} zLt@HVN|q<9%ul>#o-Eh|hBh{*|6J2((VT*z>YgqfR+0}RKdY+>hN@PqxhYpo;S4y_U-<9578yBvf;rOzJkB*H!x?_j zAKtd@;ZbSc)Jm^c{=}*ZYQ!GBMa(Lb|3J(t9-cP?JjVtn&x86Q&*KNW=dpf<*jO`s zyL5A8iW_2Np%JYVOz*K;ow-MFRwd8FPD6v*K0|Cw&hk6cT7>xzW{i!+b0%Qp0C^sg zlPNZMY#voQXCfGbtaiktL`>Czn5uEI$}vuwbj`E0$%>2FW$7AF@KacKVtmQZ1S_0j zql->mNw1&ahJ32al|rRj8ULCuhqJzcll9BjM*SkjO;&dwq7S+$t~mGQ-JH?3q!{FQ=__w zKenT;tI@wR%b(ZZo4Ugy?R?9_TM*!Z%;RU0ds#_oT3J;~>*NC~KC&7%z5Ge~EVho& z_>jAp9{Qr~fgTv=lEcDb6~rh29}LLNdwUzIBL$|s^J8BF0GC|lk?aB>!;q7)XfI!4jN zJ`qS14%!m3nS_Hj#0ConPUg>qIA}wRQUsGJY-^%E2l?Q-bf|p#Yp%OzlPA5es<1dUr7*94ZgI50vFRa_vB_OlWh!OrUm-i(T&s}UrfXAz z8*3hL3%E_@NOO^_$ThahN^HHLm-Hxst~+L2@=VxA`BhF4GYJ`Mpofm#6i8Qu z2P*@}q=D6%;#J|+y&XFq*(9~_&>d@5FJCc#at*8ubK5k9Di(H`Cdc}J7*AWjm=znE z7y4J{*Qc;X=}NBB5^YSQ2t&WyTq#g2JujCk&1bmQiZ44O*OQAT>7^b&ZTiv>P6%2l znU(M{nUn~4_~ysTim_m~+&{AHE84FF^WJn~$!^@g@=D+Tu*|KraJN85)4F|1?8`6e zXTSrdt!st*xDJO+OLY4ciO&ppTt5K=4x843kF&(uKk0hePtbtF8?oTS8v#BvVg#`W zTH`A6H1cv)ii*M2z)q9LF%fUckyI&_Wz}&WDzX3}T^Wjm35CWeVd5*|B3E$c!y#YU zjosU}H!bfP8trJ>zq0kfTyNz>{rI+~<=sOYJDLx!bT(F&hw29x)wUF6c!LA&k&&>! zaBgAI(l$Um-r$n9$f_{uK|!zJ>r*;k_Xxg9_)jr!f`^b--XrwcJibRp8FW2^T!OTP zF!!~rQu#1=E@L)0c_>rW$p(#>jT%@Ranxcz?n^YX4xA#WOsx1PKt zV+avMPKoD|R@KdIJ&`V#bIUQ{kPRz7GvKs#B1$6h(@c1u6(6!;Pztz-Pj)fjI4eXu z@Z%8r`zrsg5>oCOI9ZK@)e{V4t1i`>45ugwr>ELq5C?-b*1t|77rU7Yvg6`A5KMKB zK{+6p3Z_DObPFz!2*crsxmd9C)6H;DfvL61nyDLgGbJLq?ZnuV0X(`{&? zG+nm4DZ(3%-ngTxvm~{-XVv(Y)%E4GPWSG;^7_apZ(n>>)whx|*vEE{kMEwHHZOm8 zXz7a7Ip^zcx=!1|K6~c_!9d^@5Ggz;`1CJ`yyl{4YnB%i$8LZUK>(W4IaYf_I9DJ! zH&;<8u{oyz?Q#NHESKgKDUv7)UoQrD?hZp zsP2}bwYLpBCm(e!zjc_*gKb+rKQ{XL3D58?11o~nwUaxS+`N4Fras!WICwY&6V)9B z(Oe3g>gOUjzgR^;#gif8If2GfXt4lOF7v9(5q@_~&E!GsA)~clc^)m1XX8Cn@97bU z_jH?I;D~(&9LR`|_XufsKz3VLp!BFPDH*)yE{=<>`Zgqa@K17oC-*o)=P3&XZ-upg9 zdzB>o5izTm#H=#;Gh$Zp@Vpt|z8T;-Gr$8jIC*e#+!Pbu$7!}Y9NbAvPlgRno|Wv! zAA@6`l4k)p;#ivclBi06?s-b;9Lx|Hq+6(ENMbPZO)#R2aGIEXvA$xMlD0U^#C-~C z6JQ#2$3_?QC%`0o)lO%A0y^z$nH}Q=)|gH}$)lhRPTr1$7un!+a)=S(?L4D=0=uG$ zKf}B-U-k*`5Z?4Rn3r~kkkXJ?w-RMEnvx2aWfl}2`7Pv*%+V7Xa5WOREYUn6=WJ^{ z7Mzf18va(Ilmxt4!4Ubd2r;?oEVVcu&B#Jgqjh6Pj$#xqDygkJPsJj6nsYbl7B&aIikJX!U|Y{2qiu{ zc^khDysvCdADX-izwhLSCqKlmftC;>h`sV#tOxdpip*pJc#aKDyIbO?*xeT#; zY&PrCo@Kk(O6@1CT6;pfpZbSq!1tj4B&9H#?^0pknB8N18Stlwds%j%5n*C)gbhcu z*IOnh8{d8RUG_a~8+!n=6}<>9ypC3+DYgkN#KUuJaN>f*PqD!%Dl6e>1{@~?QK`eB zUlRA9VS|$nk^T5>aN5@rUSxxl{7QH}!NHwcaAzyVO{PkXBnTR^AS!$sJt0B$FM26V ze2|;UsTk5R*^FXIo@(I5mI1Vc_Z#X8UCx7aqL)D@p9*bwR};>gSFZjq!%l< zHC=H?rWe&K4j(=|=`JgtTU2`dIQzJ^A4$gNHKakfatJ@3fCWYVDttzAQz4LS!i>9= zi9~OyGN^m2*_j#ATlFTnMLn2DS}m~ayi^GWUm~05e3CN1|9H!r*YDqdJ%8bW`?UQ( z|2g~EeGh2e_uO#&_zm}f_hMwh`wKenk*Q1o&*N4&RiqL=Rir+r>#^rxb|kJyJ%n|6 z!W@GvX!M1>@j-o7^Y`hKI3_Am?Q}Gt@5B|Ua;D@z*jt1@a1_!-k~=;d7h>=n8=T@c za*SHQWxY=y_LHqnRD?OmZy6fRhXe?@hp^3?z`V3nM&UKSEQC$ zDpIkxMMN&on`cJk0#G=!+Q(KBD?J+zY~QnC z#TOsnxaZ*hp_?j3s%q=kv~;e`@T^+UI1(Ur+anc_HvY&@1?mCHcT_ z7W0gU=gk23neY(bX!b)@f#^r*wZ#49nC&j{T~x!zXne}e-K=Yg*+NTLVjsGeSb*JQ z840^bj%4j|V}c&5)tP(5no({pj)n$)UT6sm4SH3eigM(iL+66QBw`A~FDQ6YDzUhJMon7f=Q48bAhMg&=L zyL4Zv0{X@2ayTc90zB+0mrKMR%LkI+g^3L|m-2sQ1}eY`?4PX(nlSiKq!H#TEn-KO z1(Tt49A?(0*(Cxp$liRw-bAH0ZCCEoJ*)1{6*KSDft%;%tnDZ-`Ua(O!gH7OUEB)}dKA=#rKic-F#N$RgB-)e!6wd2`5;;#9s=#^ zb-8n^6zEY>t1>D^wd>5Y8zqR3L~cKRY$&_u0RQ;bjR#N`$zI!cw6(GkqajzxsWP%u znTXEvW@5Bb2X_izmLh;M2PX#v4P#U{R7#3Z|DVXgMT>=PXW~%hxIhukZ#lWO$y7;+ z`$bTXD5VTEnkrJn?%b#%H`1`A7Ew)Eb!Gd;&uy`lQ;vLi*@FF5G?|{V@>QSRH}Uz6 z^;A%~;v;J|-PK=N366@n2rV`bd?Bms1)&Y&;i#fa2uJ0lgd>(kJn=KxkG)!w_Oyhj z8TiCQmG+#3qe9Z?U!{5s0cTVdd1Jz=$ZBx*Hk`7lRPidp0*+Dev_x3SBC=O1>U_+) zNOvA8UL){Eh?%9?f4*0H?B09XI)3-$XHhkd1Qwm$S4@2(VU-~q1&0E6!U>7uTdCl% zgnGAB8M^0udmTAdY12Gq|1`T2jGPw)C$#ePS-{rYD9nK__<2vB8 zI9yxFi79bT_go-I|L>m5<Ep32MMaj<$6b2qBM;V0KCx>z)(HQ>I6~^Wh1BKnBth48Q=d@(OZd@y z3C_BaoB&;vopX%P&Qw-^y*N*hC6gFAv>%*fNp2VN%OwL2kPOubC6^DKS0b)Vy6{)- za+R5Xlt+$VDGhiKm=1X9$U)A6v@j_)+y97U%WFmWss8ZR$lfj6_D8N5pV%J>&$(*( znrm_kt_4|l+^j8OPw(4*^|jhY<2Qfyc<185zW#nlByLtn>RBJn3N?H88nfbG!mPZ? z;L|9k0I%hwy!fB$n5b!!Iz`OL2aR1q!F2~pjU5itFJBj(jo!RSq+>4PRI$%ROo12P z9XY&b|81#f{xncZMYh-?3(@qS? zGQxoq!}Z#OW3Ce?Tw@PzmeqvXk5_(ZSe8@`-?7s5wXYGS!#HIrHbMk@gEh~58KtsiWNJe8h%E;aj**Mk^a_W@`dP45N6BT^C z^uY<4k7G^l$^R_Bef6q4mm3MWtF~TVw};Yk+C#zO*SnW3ziTyQ3yk5-ply~Ch?dMs za>K+GAvxsErCr1rj&O2<9Wc{aIecD^3nWCsJ|5V0OT)2!S;s&1z;Qm*=APWgKkkgq z9T=dq$LQD3)2U_Ox6FTkh<0RCTAf6!S7AleD*(w>u0fqz(9VVlQ0oEk_%q zr$niaR>FJ6=5o8J6u)GE>bm8=LI?_sE1}dO3MT@jmXSLYr;l*9EE~wo9Xi@>yLQdt zyu8C}uDva?YwMQX4fF%K!&M);tG}?Q$F!AY(pJcHnv?|yg+xHN#fdXZ=yZkDSsx2PtcB9XXfz5^)uf0;fl zQEA#mN>RXD77Wm-Ys|V?^OexP*>M|cbgcsT1B#9E9PjK)Lb~@h)E_)Qzxy)j?1&OE(E0hweIx z{y3tdm)U8ahPuTvr7PNjLUXPbIuU__5aDGM7Z>3s#~8{29Rr9mk!?eV6Y_lp1$YOb ztRPrYgw}b1Agp=Z^bpp(E=!19M`T_~FX(A*)I+-L`ki%on-{N{7~THWWrGc4rFo%! z3(Ge6o*L>N?B;1(H)~Ii)HE3P+q zm*7e~{B0{-4eR&;ox24Jq1W3D#FB+RO5nBl~In$+P&JunjS<7^DhtMH16K|?LU#k_x@ESEGaH)u#a zZ%a?0ae7avS)bMu&8A}7m<`xXJ#x$*V@$%VorX#C-X}5lSuid0z7(4uR_jH*2i0}T zgwt%&Ot@IZ1afo6#3xG9P5g^iIRAwSzifj)Zo)5^@DTfJY+U4>P^DswtLB;b@0j>B zE@?n1$M0I<;H&{ZPjDnvAm8ev=)YQNQaY67(V_Cv64l{MPfmhL;{AB^Bn8$q9xa-c z%$-gbcCE`bFfcn6p-4W4dI*m4{pRMHn&yt?=)%^TrkX}ypezs!mIX4>QVQ{)5z6gE z-M-~CjN8OvpYY0avdNX9Vm+m)(b(1MBU&4uzpX92rl=^mec{m2fbY=2`t7kjzIWk+ zr6v1&dJlGJKVIA7VD~th*EVYZG>&aQ)OD$Ctg$RuT3OZI+13ZOT&}zGp`#;EEM=s8)MxFJG6q9ZPDnq7Woq=OFgvj6xOp)K}B(X zUfioI@N0}$R|du5U8u}KbXeYbwdBj-q8ZPwQ1spvICrG0>&RgHj*_xDE5nP%o14cM zg;&lgE7{TR=(=`r=-S2gwFRM2^R|wTZOx%jL2W(BsHBB>`IyejRYG>-;peyk4++|J zeE3N#b$o&w{ZM`kaG|4(b~I<>@2cyp*&o6Gk2(Hng5wOr_`Ef?mL65CC0%t9izy=K z*0sdRBvPuzT4o}gPF1F+gJg2fGwotiYkl#7-X(HD_m|u+R#9Ke*C+F@U)0%kWC-l* zT);*q7u(j7=)A-`_{&OA2`fvYJ^n(hU9fact}iDWahME|7Y#7ZmSEkfM0ST*T$m1h z^2HO1pP)h(5s`3NeN~{U99JRqn^}4yMq~i!B0$I~YI>zp-0XA5<{=U>N%Q~V!`*w^ z+79%sx;ER@(xCl0{d_~uP;biM@I?RG^v?aGb&>V+M|baD&4=&X@=wnRL1geR_F;jMO<&G-^FbQyywJDR!Tih zYxPf9D;)!o!F1($iSfLF(I-|@U{E}c!`2egx4{aBw`{;KJN6kpDtJqVe_lw_Imh=* z=?G#6<-+2^{g+(uHaQ8w7hwYnRY)-?5n8}@A~guMLks5R<(1}@_{yRA#KST+&1XnR zOv#~mshziOZHufaEcEYKxb(X2j)T3uS9WMWT-yjWrZ%l@{@bFl2B@#O^B^OG*LE#F zGWe_dbuBGx>0Kk@OHjx>`D|pbAV!e@{`L&;b0$1w>4)|f`ayp)wL5FJyUg3f=}qGY zkb9Ofk{bT|xYN5Hc4CZDvVY_V)}De1da6#)6Xq=J0Q;B?{G~WMKyXLc0d~wV#`}1nY6e#HYECeIW5KTH(SzknqbkIN1jh ze!&V?yL5N}p0%+KYm^(L#dCwPPiVyHnAK=u!4&$DWFif**RXO+)HfGVNwEV8n1gv4 zcVxj0cA6<<fHFGZ+Hvx@{fM!aWG+E-!l{}}rwyZdqPc^#pJInm%h}kI(wRr@RaB2?G=T8gQtM`XgBr8srl@OJVhbz zC-!fgS@du0w{tRQgOI7h_V71Rzc(ID$z(347u=^XIo;GPBiM^J4OVHT>{QkA&ZW59hkL-#J*_+n`6yCql&hk%3e(}->0ts{gYAbHsAF!r z#xFfQp(A5AO>mjNAX);~PrtEZ!G=N=A!MMei)I%iyk1;WTpcQ-FhhA+S%4hESd_sM zW-v6YuQ7IZ$(Q4J^ysm^)$% z=7q|G6JVICTOOz!YiWxf?CU$& z-ah}3R#R26rD4;;g_|1WPizc$)(T&Qv)fD2IGJRFc7fGq75xUI2Su7tPj*E z>DI??yliA*Ae!Q!C~FSHv51+d)R&Q@5EQ-SqnvH^mY-z ze=bg3+22JSE@w<0xumq!9H*6i-Q=<{4sw1dsuo<=Z)D)Mv2uyizLf3f>+Kiet)g#F zYp>$XqL&1x-DpQ|V;JpPWV@+>#V|PTw?l@1YKYe#7yOA5Cas3P!ka5Pxi3>VCDWE6rJ*EE@Eh*URf)?2$ zcFpVNt`XW~+OC1KX6zb4H=+Wdn|6(DR}+1#TzE7R<1HSF+tp**CFLM{n=e?EL-xc6Ds3<&*=>i=u6K@#7+)Rxw5&9bR|{{`WZTrQuD$G6S`kn1o#n(jJxr_=#&+a zrFiUf@}v-wP893CK+j7r|JYffp+q! zQvVy%lsCQMId^h$hw737HX>qj(JD$n=AqcJ8AJ? z7pVCr4f8CRM}di)%TdGE?GqAWj3qdfV6D#BXh=6{yGU?T(jF3?&i;XuA^2daMJS!c2 z^~bqgmOx!ZoaFi&N|p$n^m5o_n<>1w1Yh8XlbD6bFM{4SrEISyef6Na^q>xYpafxR4fkY7nUpf8l3&c?Ppz zCOL{dn#^|sYJVfVeu{ADR3tqXk~oa?;yC^tk4w6fq(JJ<^E0d*X_@z1xkct08Xm25Nxs8k%Zc)p4R|A>Wl2pYNhe^)6JuK#x&%xYTXrX7)0BjwC_83xOtKLrjxXneXa8P#mr+zOu)j z?~>_}j#lj@{>t?W7srXDL8r{iwMQ4eZ(a`KhmhpoP3!yQ=7`qmxw#mv>OUt)&I#?& zhMa`SRXy{BP^r#UsR@Lt5HG0p&YXFQdlP$l)T=d&yr|4P^&Tkxghd<>0%%|h7Iey7 zV&|gkwPM@y3ona}gU(rsKUzEs>x~l+j8ouaDvC%V(#x^9v_KD?WHv>zL$cC3lKs^7 zC9~FS3D>b7EvjdG$F+BO%0b;$X2*qA2xA#Y8)OPAX%M@~3O}dA;Ux(Q{9NFhUQ#;n z$1VL_GVoDlK@{uwrf;+w_=m-rA;-Ne@r6%h!IwS})Nr_Z9Fxs*T6^~Jr&gsFpg z@gkGVpUzb(%I3}VB9jcLmdgSa{XwR{R}Pl=N+z0@4a{BG*f!X-V=TO-*jF-KwWPmt zQS+jKhCN#x<%Jam!G@aB^30Uf{MJa@iaEXtUvAU9K**b#n%7d_v6MI}DJ4DIs&h17 za8$tYKAnUIU_Sy5T|E!KNh2b+MyDR;Dqmo_1sX3mnhhMLz#doV%8SB=jG&PkSQmEI z@LRM;6O4I|`ZnNd$Q-lIGUkLFb3$5Xg}B2&{MD&OoYK;RSl3oDo3jQSa$v=023)KY zc}EievKel}IT0sNP`wYWLe||JoD2@tg)FRrNE=4~^SWpXDg*JN6PzsJ8PjJx{CaQ( zE>@sX1W}C(3-TzyCt0|AbkXk4_S%%nmf;;cM_SiStXn!V9zM3KbG)%V!VYg;x@bdv z`n>d!CH*U_*y{f1vWd-_n_W13v~E@7q7B)Y8O#m5f&C^l`G#l;JSjwiL7<*dK*5zF z4>1wY5#P5l!iELWJlL;bl0y-hW%~Aw@i$p4Ol1vt9*myjRrHUw(0vO%oxrMFD1%T} zA08hc=;m)y_Mow0@{2N;0LrqkhI{!9N{)iJAaWQ=-*C;Bt0$aDUT~x~gtbiNVIUGL z7Ksb5pBN~w`8JA?A|i=F6^6}huXlcNSO4bC;YT0ExFw~T-hstzr0U?oD(z&Xu@I7) zhcORgOh4~KnZ4V+ko|7zYCYz0HEj#vNV+Z&4#|jn0Y~gmz(ayw1J1c$!XcwH4+GA0 zxR>UqHb^)kI;Yq(%0cV~yz+8T?uIOAElW#CrQRbVHXpL|=p;BewhdAmK@3ntH?WRy zJw!FP@G8tQI-q(LS;j50B?-#7BX#-PySE<-uOD8!gCfERtQ@?8)oVW?*PZ3iY~@&_ zUzQUWps|mL*~Y`)o&kPt2Kd<-;FoOh7tL``neaX_cY~fO6HYvd(Rs!Sr+BYU|3wS@ zHvSnCuBpA|ylyl537~o#aNE55c&uIQ?^ZezwYSo9n@LZsy`3(yg^Ay>zn{0zZJpm$|}Bbp}?ZuRlm7%~2(BFMM#E>G;GBKfEgpM5s+R^tzU zOj3*_UN!p(_DKpBZWa1T@Jdm)^s^5N)fUj zYqji0*d1~NRJ3!vhCq>E{!2jZ_deor&d!MQg4qvVB$xg0*jJb0`#EFa7~d+vjD4Wu zUCcODiGS4y5n7cS+_SuD6mkc=p@^?J;`XwyfAy>LfAH=3|NPG_-~RER{={Za{^A#t z+Mm(o)EPt-X+<)X%4o=wmW20`pmfAr^x`ZqlC@YLy#AUM3{Wi@u9^!6M}!cYo9XQ& zK3n@s(Xyssdrn@({PHH>oVOZ(^doTgE8VTRzVf-1k#?RW77w`yIVSDs{rV0pC%Kyj zKg+Ff(ijr|lnqX2zJ#B)!AY(qe2PDqu>Uhwe6e#S{zWUCGCz7h7i@6a$rArO!Ldg3 z@Z)>nE1m7hIV<<}NNVr1XMvukyp zRfxSB4?oAPaN>=`KW)HqA`y)Se2UkhKZh~|IS4>I(%VO=sLnn_u@9FsjU|cd1iGz` zq!g?RqzR8wytWGtqQe0haXJxicDa)95K>aoP!cSq0RAqYOLDLiGhnTYdS=aV`VKgUZ+>>u#sTz&mLt(jiLr|ZK@I{9mOENNu&LM+|& zeCJXlvS)Z=&C!Nwo><6I%PDT%;Y_vF|KYCe^!mR9Uf9g_e^4uy`oC-AoG`2@#lc#| ziF8a~(*t5nPu zXxUX$k|?L8K=mX|;7*n*J(v)}a4D*B zouon;Y;07N#`eax)|UEOICd4HFg-AfF3rmNF&TlQ(kJVs5~Cj7N4HZ$Sw3-N73I(_ zOIdnld(o2XmfjTfT{ReAjb3_aN!OlsRqM*Sdd2S<``E0VLkq{6?v)kkbC;Bk^epaK z99N9qzQ21-@2X#Vl9nzRM@iBfdOTMA z-@TrQGD!oac;kUjc ztm9ZJ#F77^m@th`IXB=7&2cJ$=7{w`8dHv7?MYOz#`RRqr}v~+vBqIG@NXy396_Nu z?9_!Z_H@k=qmDF(b>5^oB!5rY;B@*+xTuU5eA25}b-0*i0{)$`_9HZk?B}8tE;NdS zU$((Xqe%D#D_nR81g9!iV;xpm^DhP^2{5bCVtS^Ac0dSKtOujMn(DG(Q6U~XN=F5& z8yOosKB+kLD%KLSq&q~{IYic`!pz#trFV7;K^SFi5@CwRV^KpFuBb0Z)oPHgLOR3~ zM&ZPTs$wIFYccegg|VGJWv^4U?<%SjPoDFZxwkURO4ZJGs#G<%81`IhF8f-7eMT0k zGVqKQO&&71P~~Id4Ji$Yb0FMZU{{nj@70Ln=aD;Nd73w`#{bb;Kg zFEwjRKaJYbf$ky^7t$+B`Fg#sRK(y|4RqRUs@*{Kf|5!#PW{C96BtQWI8}%Wr`gJ^ zfY$*fm1DlgOPoI06RxSr&vncxaSU|w(aA4F8dEA$NdxpF7E?o&J9HyCapewSQ00z} z@fI(OtK30NlAurCsHV!D{}$Q>u|rw8qqm%1xr1sIYfD6@jh3zKJEX;_rKsG=!f#YY z;MNCGU5hx-i1~M_+>z~)1+98J=&K~w9#^>|vRIB;bV`dXR!6k0ytv4hta3N5iu98d zOMZq1=yM*FrUt|5AfWe)WcCW5VnIa@rLR)3n&xqkFmE;eu#A0Qfxa0D4 zT=oJHDmIGfS*B7It;nR(S0pbuUS_2)g+Ndtt$JD>)1}wD{7!6GBc^DLCyt7EqAi+u zJ{C))IN=r%rTdm1OZ?HaSfZqbVu{}pE4zRP#mXYKB4{+=T+d@{gNzaF2At_|mF6U3 ziReef62A}^OWXz-v$VtsB{M85Aw$<%dUO&TTMCq^pMa!>Snw8 z$$eM!Z}#PE9=Kv(cz9%FWq5e?$S|TOzdd|pQAfw3BZpauc6!_9E!$b1_QsC!&D$77 zW+gpj>7YJRSUQzDr-;rN{H$t))7d8RPubux{4)d>^KBLJ!(wGDnwk!&u$8RKXTybL z2<;#D0!=;~TXFwgJP-ynY!)({@H~z2_DnHu!I#C|f=5u=D4LGF0no@Jhpt|HI9L$a zHh*k=!Ng)S4r?hvU*50)0oi=*O$x|PkuKN<4_wCoUXlDUqi%M1EWS!`;*Z3S#Zl=c z9`f)dd`kVQ&S!f+vA8AR;*6L0v3Mon(^)RzvG^jv>8z1(5d{SQ>#+!Q1L6bky!++T zgLJOUbmyI_(VOYcyBil6-0X+i_`^p&c|S!(K6C9qej0I+A633s<$q=F%ixq$P z*WbtAIms5U>Ek`tqN2{;w$=r0kJ9~<4ayuHUPTa?m;J@3U>TAS3nOcTN?2=NFQYet*O3KBBA~!vA z9|*w}dP#exFT$K*=4#k*&pi)3z$25dewVds-vwu6{~9=R3SCl^cNw<4$f3@a8Ju{G zw-aNi8)P2}y>yK}gpaXIxmUjBOnp)H+P!+8h|Y9!eN>~YY9Ve?U(qzsCWU} z;M%qC&o!g(c&<(FyBamBGxROEwqhDJ4LH^J+o{=a92AURldgMQ-LC+scg`xXgg z?UKP0Zd+~smh6*kfaw@Qaw7Vus+61reV;K}#z_*z@uZ|*oB@NI7?%@ch?83OabY?J zGD@qlUnN&mM2_SgDY1*xPP6ZKjF$Gft2db#?*tw z-L7P(Ya*2;V|OJpTw%mbe#&y!Sl-jFRb(ZxHv{D^Xqrxn8~>S=B%faiK$GCrSMh&% zSWH#3w5!gJH?mFaQSBtF(q0RvzEo93YGvL}tN!cLtV8=2>0Hbhw9p>^o32@2vF;H> z!4L~W_AP?T96qI3Ss0y<*La}aFb@ab$(x`WL-9Ikjf_;&aS2*;bJNpvi*t(#3(|e* zIib>EQj%Uu8Ynfss8*IuW(leeGU-PkJHnIN_wLfB-rcmTexCNT=lYf{>w9{5#SebK za(Y|)hInM4yRI|+kH2VXY;4ww541M@79@*rfmQJqdit0bm<+dqPxmUx_0$k+0k)j&#tbL@?GuDffFm1 zpBShJ`McWpb#?7;?F5FR(5%bGl@QIPZfhyN)l)e>$mU%o&$dvy|cgn&e1>oe)*wA ziw-S6$s@hThgTl&ty;LJx95t5S`c%C?ZW?&b|M58^^ES3pTyU3N_sGR5JIy)hp%JU zwfNuB2lNrmN%R4meu}b982tb${w;9a&DY_urJf>=)0j#?eT9Erj_tT0#@2N>qz}|F z4Lzq)?Fe>@`8Yg1fLtTrKhU=XdNSYcO@g0?Cz_27%QWpEyBYg*p7ztKD!D}keWG`w zBSnn=2K+|5%*gR$7;k?d#<}Sj@BuY3TZ|&)OXiY|zGCtfKib*pePKPj=)5F~#d!mY z#d-Nhai;0gPSs!fd3gZ#tYvhpjU3(RJ8tv@eOL1t`zHRWI4|R{ zx2Q(T8Cu}f&J?>?@BpXwTni6y;_B3$w^Ik5M!I(#n$?F8!ux#DuP#%Ads0^;ZlhcP4#hYw3 zruPqGAr$#az9%zS7IZ_oSiY7he1B2eCp}em?Y^t7=((meyXcDO@VfOI=Kd@6&PSeQ zi&ppcZtvak$p@MJSqW)7+@!|~P9{mt} z2UP4)3ZT(zCpg?44pF6C&jxX-2Qr+PJBwtCvJdknzU_2pll7t93s>K{bm@miI<6=y z>AnJQHg#OpyQz)kz7{BpKi=!W14$3@piJ=KZGPO!g9I4oJ`m&VbPU`yrCEznaQZWT zqkj4moOFX6{q*#ff?u>dCB}t>7!m9A zLL2^#kfmdSswiqiEUs33RZ{q_^z^z1mKgMz~!o}yj+eB0)j658+5(F zp0Qi#CB|qTXTo~N=Hdj4;n+u=j!k4*{vPHE?MoLobg?nzlLal=H z|JC_m9eWG4CjYf5nA5dkWHfT!?k6GqQH_ zXQs8B4~*YX?TDn;b#WSo;2Wyfj2X4sF>u0cMO>WhK)O?b8_M*`J%y*Bo<`}KGS(Se z0ae(X*OM~L^kk&V>+voKU0e+jt?lcZ1HP_}BctJKcYh@kal|hsR>}h&GETc*uH}3? zO~6=;oB#h2;|Om2zbbauY_xQehpM-1vA2YXIB^cOqh-1>FFKdzLM71{jj|SED)R1u z^r$cq6kn4^6`Kde4YLCKhjUA62U{9{#MFnJRiWap_D;5Go_6FU$)X&CWU(9cxZO>R zs@_e1NbXd=1XN7BV^7gE;bmrKdNLh2PZ*vahc03f6`zePydL-cH!OIMB`;#>H{8o3 z+GpA3DMfo#x0lc-jfz;L&@6kbqk^sj(8DyUzz}+PkExRg&Z(uOj=he5JC(d>4j@90 z2oB-6gN{_e5Ymy^rO=LD+MQ4-?@@e(bja(10?5R49YPccj6qN!G2Y^zpSniZ)Yp)v zmQ;$-1&*L{vq5DHj^5`huT0&cx4pw?3yYg5*V{rf>TU6L*yGwy73N&JW(FEoGAZ_K4+14p6WuZKxD!2=?A79Ma?(`Ip}~v$N)Se73pnAvOE)$BH{4lBEweNk`&x}HYyBR(khK3+Ko zaprlJtOTQ_N!Y+#uNkco)wgm`y}S|2g)wQ^2=nmXO6}HgCA&_H_iHVPvC7!53<;8F z(`vbX(@!38ijs^;jC0d5AY<}u0)|*U>$yi~y=-}UdQ0+SF#zV;0#=-n7}c zzks|jp7B-a-aQ!15FMB7HVv{YCCh_KS;=jBCR#qR)6{4(Vc9@`*ynAS)7XHsU2@~} z`i*nTJ(=xM_9GitAUhYt3JAK?ZvHP)9^qkIXX>O=Sjsryq=V#4&)a9J+w8Z)W{JC9 zUuU^p534T&M@fd|c70N|C9ae3cD)|jStSBIGPKikprhkJj~?2AfxhCzKy};xuI_#9 z?fVhqX#>4v1Kuun?Ph(|KP2=<%sY+8c$ukf6`#I~Hvg^d;!K`hEJwoQ3ynr=|h1!o<;LImTM7s=Y-R^GF%$hErQ=0E5mInUDKF?&?}F(@*z{={dp z5ol)Qe}m5n!X0B@NDWtHMVef^l;J` z2!O{|R8&-5G_NApUtVT#w(=#CM&2SQC1C7ZGWxgG6% z%at9`{oTC>JFy+u>0|xa2+|>PIs0)(KTzE?Un{}RK-{x^Uw8b@pmU3IQ`0#>yn6|} z14csL$(HZOJBbm;JCpyUMC3n3>VA%k@1pY9l&cm6RVm4w0Cln(&Q zIEIiJSevD#O581eGmi1UxyAp~9YYhty-Q=a_?Nk+>rliuZeB6=k)r(GD&w=`ePJm= zlkZqns@yv-V?KLy4siDxPYg<@C-KEkP$5+}iG3BP&%_A=JYdYQ@|HCrE%f)S$#!#1 zj25I(Y-_^pnxtb|3sd(@Uqx(z37nLf*zb7P)rc8W(pt}>P$87lDWdMMP{fnJh2A;l zS5(VOQ)Kwq*y~0V8R>c>i=j?vY{OlHgLiFUf7YI)7)#{Jp+oKMhlZ-E5QHDTsh6@q z?AHtT^z`gm2-(8!;T52Y?)1Tbap}2h@kPc_a$AIX3pY766=7^|YNjV0xXB(wo9MGT z2>v611Jfx}By`Az`ttSH?|CTvqcx+W>mGcNM<&*^>{4#Br$Gw+{mAaL_R}9Kkxv1V;hFv%_D=_ z6S1))Q}^}UGS1u|ELY9vmA^G8k z#%&JjUlMB2KWE4vZd_Tvp=srP(nf6vIi$)pQpn@OF7V=EyV)_ zs5A5xaoQ5!zN*w$yuD@FvZ`of`(V=*Tf-Ymv!WY^mR7a3v<)@xnQ&H?mKT?|)&{G5 zv(gI}H7s78pEtK|A^m%Hdf~!w*9f>SsU(efx6bue!F4?sVPL2`BnD2O8r()FO*ap} zMLb2|)MxAAhyX_JFyQR%khrAMTSGJ&h zCho`^Jqan2^Eo?xEL0y_TQXZq@{eT8)6-kRx3#o{j2L4QTyH7tWWaH17(M8AJY-@w zEd6fYsFW#p4V zQ-Kr-s|~l#5@9$Twbp39d?dF_3F5LoQUr(#8WH{2RegNCthwBJQQwCuH1@k~+gEos z%&uvT4h$|1m8O2RY30VP;p6+8HV5wau;|F(&>Bc@duwZ3%ADu?+cs&>u>QSA3km`c z(0-tTrSD^Jx|M<`9+twtWEvB5k+I(linicOFQxQe?+;&L|M5zgyXVcD{2Q&S(b}(m z8ZD8_{mXlLG~hiw@*putk_m%yx!G}2=77u)1p))wfdfPI4T-SAnE@9RlXoC{JZ1D~ zY7GY@!qO5Fq4$WWh6RqO2H^Vo&j$q_ih;}6H$q=lHGr>FdIMgm24dxu@@dO6*M~Q) zT{{}SZm;vk!>n6-ZtKLhZEUgjZ-;LLhe)>O3(5bynExBdyJjUA}h(Yi7y?-BP zKM>=Rfnjn@(m{Ncl%7g}(TBOvN&!Py^|D$aeg*w}HtlN$>hm^KXQluQv^N!1pyH~mvKk=f&k2ChBzk$ zIoq|c*8IEQQOFS!OJ;vXKl8A)4x*m~F9=v;n8phN6gWk1vP1N#9(c0FX5zIWRq4rG6VDD(N*`F~tIRfeAXLX%)DNbtQG0x%^s$Jtq4AK^|k5dMQ$>aDw z&KMX{D`2KnHSEMpr@tJ4(j!Q<9wOFX4iMHXUJg*{U>%l$Ax%|vpg{d@k#b^%D^!BB>NycMPi&bFih<^ z8+wdpBQS*CT_{e-_|eZyZ%KR*T1xhPF)@bF4-(_T3>Xw|kr?Og7~pm_xPzBaW_n$K z>d5qxviR`3<#mC}T0Of(yNSJ^-B?u>V1Jkw&8pB^a7kZ& zJ&x=q+(PqG!6Q6{Z1&-a&^K4M&& z^^f#f%UDP(_8gD#HcyB_724DXtoM4tfL^b`@5B|_bY6Cd6P8*8a8BMNMmPG7MPkJ0 z3Hq*{xo?`0IzEk>2DC_A{Vm5p^>-Zo;rAcIpdVWO956|1&CIp42{Cqx$g<>GEnyJX z)LApc<7eWU(RV!8C~{=)yL#rn1=pyW&EBGczdJ)bUUF^iv=+p*#PRreNxEe4#MDp{ zgLD|E0AEi{#}JZ3ejm|E78WEYfgGPPTAI2|(yO;b-xp`VpiI0R{c>Uqp~ED`h3Ocm zBO4KZsh|&aWP4)tQT4ap_d7;Q``oGeTgP}eF@~@uzr2rVh<8fceH#GX2% z?TU(WZ>Q>*m{ta!0zp$o$pbMS{>by@nNdKx*D_20^ro&=j5r|5ze%^KYqTr1SL(0X z%N^QRM48pmwVNs{pL~*aTy?DKKP!Iw6sz36c^j>aq=_`lTe^lh8@E>wW3g7mI>yY3 z*uMx&U9O=A!-%Q;MZ{EaODD_ARgUX0YK8bp82*f_|KuC%g|Y~9QaS@4t__yreObQ? zk0R(7ARC&Rja-KDrW+nt!#yhzVL|11Gy6;MP|v=@?Tz#5>bG<^j{7r~jILPS6aC0N zeJj_mTeQ2Rv$T9oPoyU;y|bpOXKr3jMOAw^++5k*)YKj*rg=+RY2N>>&$}<~Jc-A6 zn|~0-xda#?a6}(P)YtZj(Tu)g8YzCXvkCgT#A}5{0>#kdwS*z`_)li2uMOigvy6`2 zC`UK?jvGBe-_?BPzOmLKLujX_fq!F$`dTqVQD19s5e8L>>uckAa>?L{xho|GX(YLd zr)I#QGhbqyHZV-hBe_JHhv*`m1ui9!kTVHd5)bs2LT4qE5aE}}(Jv?NL+C7taUn5= z$Yl_HlwUH|z0dBKnGrt8Y4Ta@EvnJd=E<2pOBk`2N$lKa<+^BYejazI>DaZkHTbL& zY6)D;42F*WP(c~YAr<8Wc!d(5d2-<6Q7)HASC$Vb&|0*oaV51N>Ys+^RuEmqE4FYKya!Vg+{qa($kR)D#!J&5mbH5G~}m zod|Cw0uC}vIZGGX+j_9t1vYPnB(B|Qnx>k1j%PO=#_zJ$-)Yi6dF%%5hjqEK@mPyg= zjC5I%WZ|*y;1lt)$T?pGozr#mabeR;CA|6+1XemLW0pDpxV-eDPKTkjKdT!qsM{ zQ~#gxWeW=i#T^9#r$XOydf6fw6~2Dlo20Lrqe8CH6-##O2ZRW{i<1FGzP-DLZe3w5 z^6lK;QN5;=I5jQ6SY)iK_u z&X+Jm{v)o=S8w?~b-p@A;yT~Eum*AlROkEKgmu1A%x@!XkW?iU4Jr-_(xBE?_%9;j zTgnDOn|WIWuM&9iW_BsQvqhobH?G$HaP`$}4tHz8sw(#DdGkn98hvsH>caGY@1#?Y z{ta%v!7f_QRtQ6Utt@KZQ&YGjiJWr?)Cue%Cq2yORFWLdqzSW9wjx2LEQG=vaR?$_p1nl z&U^e8T3q&JsVj)$yr;F5EpU20-RKpSqI zdfcpojYRA8I#}aHKFaZNWesK`YGD<=%vK63-chlADN2;WZYs;|UO%!iB1&Ob73Noj z5mNuSw*Sace!I5MkhP6;e(H41x6?(dg|p4V6?dinDurAD=y$zki@hbp&^h%bQ45?q!zprcB0#r}#9U!qSuxDudu0TR9pAcGfFDM_Cw!T~`~ z1T^gnuUWo)ZFu$a?a;j=WUFwr&DE0G zS>~VQ3(>|m`0##A+~=U==Y;;_>hS35wVT8B1zSMzk$u_|Z25-G6I--rkp##4ZA2x6 zR<2GNtqw8`x5Teb9LBln7|4CojQkGtA$0%EVsFa6VV@EleV?A*5{P+7jIsQjJ5`2hEO)GqBX~L=vHIg}|25M^KCC_AdB%R9q`k=~|H$K|UrT zKgfg|MK}P6ZCN;RjT%wQR}95>s{Z5ZhpL8h^KurIH8`OTAMrDa9MZ5?c}g{8VQ z|1ri5M+yIe185&i+32x@Wm#`1PmRzu58$= zIC;j&x?qF3mF1o+dUJ=q_>SPOrJloO|Cpq&F_O)1@4jJKWa$mPsN@*AW9Y}*TX8G6 z`^t{_+oN@_9vyP++T|QNHbgZXH?KI`vAd%S<)iz$I(BtBKl)KHm)QFnXq!cO#aT&i zqKn=c$1fERn+t--sdqp|Svh=ok4t_Ao4vSZM|j8D%$+ygyo>!L?4<#Xb4r+Xox2q2sg1~k-^@_%{z z62Q2sa{qhoEJ@Qg%Vgg(dnS8kvageUPqQ|A+B9j?Hr>*dwm@5G3$#!wh$w3XWf4RW zS$q~1R1hEXR8T=sL}d{~al)g5}O!cOUg@}Y}mM)?{_kYtS>0KWE<;I9wy{uqNbZ_ zrPWeUp2w!R$R5NgwP6 z5G;J7xv-V?v`iEy@QI7C(yB1IfR$ElaRDo>+TsFMS~0FqveIJDZidbi85Go+P{xZX zzL&k$QMIoNSVz?s7qE`1qxod(D0$vh zDlBaAfxL7JS_Scr-!5+9v5is-exB6hTb8oemsy?sd)PEg5^_AoMC05})IoE|SudSl z)?v1oHXI^|2i2~jeCr!E?Fk78TsI{cQ!Q@tjN&%bLE(! zxX^4OR8|_S;gwmX?#kL7YfY8W5%DcncgmKryiED`<>lWgN=VIj*mGn1qYMdAamt(S z((+^@jnj&8?!`F6kO&6x>Yz4SICrs0l1phu8)e}-n;4$JU1vjW`W4Zc2CpSTgcNy; zV2}2CPyf2Jr)AX@hz! zl*WTo4f#*bf-8(7n=-C|xq&(rkfO99X~-a=CIbT79=b9zVr5ill*Jek9t8d-l9Y6i zzsS|pXr){_W^=^l@c4FraPaWeN0dLZ$W2>*`fK@Y_PMEvsm+<)UAHUGVAXi9Uqr7W zr%$2V8JN=t3&hnx)VoeNq60(Y;wYhy#bPsh1On+6d>=ai?8Sjl?kBxG!a)paMB2)6Yvw$ssQZobW-9)$E-H}jnAuWsfa&|Z$@0QPg>RWR=u_hsqvOeJOik6_& z0MD*LO=Q&F8-r2`Pzu&*RFHAaSrnyc3Y9{m@ZN}JpvdG|f>dwCuA-viVq=6w9|X7s z$sgp;9cKB=)=;buLjJcAM?QN&xkaDz;+Zoq>R*5M?YW&FzhaWtdgeBP5y(=WmZ&n# zb%N$fzcJTv`6hmzi|d2l>mhUUw;AoEw<}?pvW#wW> zG#|Kupb~<#c?`au$S6h5HVA0N|03aWW0t>oi}RLuDnEXw>}R(+Z~1xY+aFi5cI9jQ zZzJndzR7C&--F6$(Z3a(V2=DfDN`zS7a7xVoMkdu6M}k$NNoXVlF&*TKJGGW_4>ks z2S6TSs}+gBRTo%d=T#Y0;9H@Jcq)MqnTUeeFS}b0G&LM6h|bwpRlcEOvvw{@ny#aY*q)lgZ#DkU{DEiJphw6q6-Cmhb-0Nj>I zDelA|6dZ)mhP{c62|Fff&JlquP=~^g0Vig@+{J#G?NZE0#yEG2ykV|hUO#uMXU)FB z@pC7~&mq``m%1J&5vtD+=Bb&c#Exlg zNFcUMhwO(sol(~w3i1+~7@C+69}D`x^^9bq) z*4NJ*tlH(Q##es>JL=BN9>i>DR`Q=~N;C5EGD>UaDqlQ9^HS%C2?~lErju0u*4>i zxL}zGvg^HrYjoRZfA9#G7UVDy!exX}6DoVkrxg`P%GbM;&n(kho68ep^%3DYr75}) z0H|I*rOcoHN}oPg+jPreMnW^;m8Oxy3bvVLaEZbWXvLy4AlT)ET7VYPtTCa97w&yL znYB>^NDYD9cJ z{oKg=xtI4-jU9hC@5dNS{e)n4g}fitx16WA^AUDIfd#E8G|GkNkZClNzy4GXJs&s! zH%NwOpcI7L|P0X!)n{8%2w2p4Pe-Q7FpxhGS8mMqT}w;NaG zsFF-^ggoflzhmDq*OrNy1B8k!t8mxG_ulFoFJcqu=kj^fV&U@upGO@Z6-0<+DZ$=| zz2$G^;^#C75+%0L#O2L>#>Kwl%K7>Ics=slf)$|m0s)@m@3p#3dK~g=5_&?t}>RaVgeE-a^)t2DBCN0r5P0)yDJ-+!oF4W_KEX zbJW591Y&u;gNUFCifTkOTp`g^bSU3@F8>_O;C^)ibfy6MpT^s$k{0+)$Rb=XN(`sq zH9hhOkMlDuj|dM%TZ;4J=4#Gq;_W=z4$P)Yjy!VB5#?{Lx5u{p^cTNiXSZ$Hz6;El zq--A@eptB$J<`5c&?9lU@Cfoo`JqRZwS$Gv%Vcuj$*7f)9^Xle>O09-jS3 z`R{Kq!`q`r zeq1+TA0W%SeUoe~Un)2z@9qm0LM`tN0X#uCC zU)2+)xbgcI`DqrWJuN>ipZzvvagx}R@>$aO0z>S$e+2DQNd8Pr0IvnJDvp#uK8yH- z07eV*hr%CLAB#;W{DrK;*)8x`MU*}a)m9L91al8Nar^M_?Gvkx<)rnW>^iziqa0%g z^j$|fuNg?oIkrlEdi3t8sk=w>^Gtp1SM_EhAzGg?&#CSo=@CHKlYhy30))#zF))~5 zp!m^(nL;>Zq2w(j4bmzFg)mH()8&6LItyfJV?4bRI0o%LakrHHA_QkBakqNhfnkdl zPG!{9lOFGiM(zucR5N^YIb(@kLsqRItQeWNk@eaoA3Nll9vMA=bEsVT+un`ruI-zj z|8D0Pg{7W+rSjWJJZJSV98zJa3)Tbk#7|ohsA~#^I!J%>5PQGsZ zAW+-TO*GUYIX7PqFqLX!j1p~!+%dc)jxE-jnoLD;FziTM2)D?17RY8-`KT%xh(@OY zrotU#Q1Bq=j^W`uCdO|a)!ycE-L4<|>{!q7j*jC!;u8yX*ZTE$jSk$hmNQ)68oQ}) zZd2E_gM-(0iBH-UE%+D*d@1@A@(*g#^5jNXwba`0-|tkGcBSHE+Bdvuj}YVddc| zc6|L>^t2px0(2NdGD#50Q6V@vs5UOJ8jBAqMsC}C??|N6``t~cIzcXa0E*Ir})wte%qU2>V@$}1g*mD!P@M-+s=$xxw4nhwAy z5jz=C%`uU3u%63#ss&gI4pt%Lu?0t&AQFI1g;RcXquCD3AzZ>vz+#~u6~ks=Wa70l zqo$*%(K_3do^QXgy`i{s=t~jG@8vs`cO(0*$Wk6-5p%D9cJ+Yr4c74G&))kibJ#4( zPuER;k6CHQdbmy#ahkh zBA^CxbE12N?-%FisYL|W-7qlm*h#8+GaA6gFG2&*0+2a=edzH>vqhEGSR|RQMdsfi zjX9>2$q%G-lqM$W~O%it~WTOC%+M;)Dgqa`bboKzYHnWeX`Tlpi{j7n#$+CUWrp%jYkV zufY4$h;FBm|J@2EYnP)Jh{#0YfaF+Yln^2RIZu~bEz;Z*O)%Tif`~)JYN>>QGreAV zK(vhp!V)$&@2acY+nSuVEGfEjZBfZYMgDZY3$;bJvFodLclI1=)Mx3+Hr3azbD7P` zZ{@Q&%D1tGiQrHe#%I#*py33KQhq92w?aEYvb{+=%dc;f?huD{CL3yE!1Ci_0; zpkxSl;0GojGmvKjT*T6_kVfoaP!O(=5Yid;5tT16fg-94i}+ek8>kYQMlaH36M+>= z><$I#QU6o57n~#FawhF8C@&yE9jdJdxiy0Lk8UHzV>WNS!beEFJUpy~PvE_b>) znsyy-4$jn9Fa%yEh8h|N1FYpY2<*9E7o-7 z3R$PgO1H#7<%%?SaFl4J!)y(xg;lWhyciKq;Y_JM~w?5o?iKBu_4EJ%sl zKEC0S!ilQtbwwe`3FG%|+4e+Xvh&{cYj5faDg64bk?XrUt{K?-Owr1S3>NutM#kfN z+xOJe9$1ALS7Wb{v<<%SSZ4AZym~&p={L0>son%+!lHJd0Wz3Wyzff>zESoYd;?$SSHprv9Kw;dhgV=T zr_fa{a(S2o*s|d7$sIf4?%DQm6tyCmY>5UW0$PYs`h?dE32!A|I(({QBBVHC;|Px+ z$(?~DcTP@t9}IUkgt_Z;a^~(NbisVwm=EbiP0*%s{AVBW`M3d(L0pd|Jdc6h22={d z5^JH%D8dM*!vqoZg=x`N#8X1{iT@B+(vHJOZpKKsyc{NYTd$K7Fx8LiLYr{n zX-Pc=e;q%b)|9g2&EL_~U9c=iGo~?#uJtx-eFry3$uP_s2Opzf= z@ErTtLw54bD8MhW=(xbW($NKiA4GzN=F@C$e^CGNj>=04t(Dg{j@&V&Pi*N<%}XgO zh}NyWeSG51F{SsaZmry}={nZUT9@@6YRJkqJ6&C$Ue{Gw=v|l; z31Ie6zz<4ToJAlWAR#&#H4#+ZA~cBLOUSMSZ+JSCqRkMb6EiJD`aFt5Qr@WiJ7~5n zpKf;-6^3WoGo7dQ-s5t8>7QlF-~aQ~zWUB&M_f^E`^$n+=OkQ=rwloERn2}hSWbRY zFxe}z;=I5(BuT#pkag&((ijlbNaN6Pldzwh%16b1Xv;}2Qk>xaUUYSrSLI%wl%Ioe z%qM^DMocNWsnk4ibMlu0iI7$G3Q;!M&(n^c;r$W=RV3%^Ovrk0UBtg+U zq-3Gjmt8P{uny%>bclHhy$eye0X|R2S;1gI%@lh}{@1MUw_8 zB7zt(Y)0$}R|L^-u^U0TBjkbo`TH)J_0uGsd6DT0cW!v=&7RRBHl)t{fcEx5%y>9ZgNunTP~sAm;#Nb# zMQ%0J1P#fnInIjfBp;JC5%RGNF#zqC%y z=Qg%q)z^Q#oo2;HOWFYKw=sH}?el!LVyDt|u~S1dsr)*(Iq~*_FAb45;5jB4F>5QJ zlW;Lv)4Xk@!9!*0v#Yi{YWvXE?xLQ(j%Lf_ETVh&I;Vs0s91M*&{xryr`>UAzp zam@&6@d*|`&cb^GpP%;0W1waHUH)lG#FFDhnQiILu`>E=qw$QvN)gs16M+bdr=r zbkaiPayf)!A;rOshNy>-tO}uNC3>O?b=%siQ_Eta%y~J^?OV5cad=4k{LTgCKJllmzk$#R_0!2q%8tNCp5T z26BmEAWT!`2$C(`%+HHk4QG%O1|n%Nwn94~Iu|mzDNsr=?}4rmU*3 zY{+V9j0xh5(M#-mV3=*bXU!2yr~QJ8!@rb9YBt#`x>yUBUfLc{B`$k*V}KuJ2vz3>#v_r14eZ8M$9-2tAkoq zFA_#L}K&*2D~A?2lv5HIW^Va2dX64Uogh=acU$|7Lgk>2{a_ua71z1vK`RHzlhok zPA_i!gEMLMtVP)eMh#f841Ah|epOosWPEsnVeM9cw!Vk!%b{fe9SS91C=q&fl%udd zDA28CB{DBk?nV3;>~U%#ehNlaqfsNjmp^x9UMpMfVj**XWUs!@-B`F(-c7hj`CY(4 zii|j*AW+fc4}KI_8=@u{5@p0OP&H*EsV=|^U#`5Zyj93bT&Qb&VD30!AnXQ}^Dk@K zQ1d4m_-!X$na`>Ul`C}aLOJ5Ir_$?VVk~BpF(xG@CBbJ+4eqB!qUdr^28b zDo1PD#_pZTpSfpj>>hmGJEr{cbXV8uG3Cat>&M2f?^13b*JWhr#&4ULxJ~@)_O;4O z-IVRIM>lutudeO}L#*q$x(_fQ?4;9d8Tp=J!u%{En(DEIgH@La5}sNqWsZZ z48C3u$3LysO4XWmTAv{`B0`cP;v*-a=jPq2%Cx@z!{#*ja^56(}6 zpF4W&a|H*tZ$H5Cg>63lxzC+eKEt-}zx2}m%BN_C$~y%=K*z@jf4bc2JDoNmaZlh(E(L=FztflcOKH;HbSr>UO&bCzgInqs4Pj7wxTJ#0KF++D{S zYPFctQW6tlqx~FqRxa(Zvna9w4oIzmt~>i7UyK?L^j~($wP$AAepjIT&hduEPoH3S zD{J;1yy6JE9#R%y%VFgQn{dF5cCP~{aUa$oQnI@(bjo3q5gLG9@|r`@@Vs$W;F5p= za^y_~`!>()y{7-B!}8Nu=Z)-kzRr_5wD7>XE$HV)^s^FJhdQ>gLC`Q#nQ)=|q)2K} zMT1)|L9{vC;zQ%240vRP!Ou)>q7WMNY2a>RCWJJ{xR`R&wFi%Ga$SF9$FBXGHtiX| zMt=I*{d=$KQvS>yJ$y>Z*fu#eyL;1)AwU8G6{^@0)(xyA$ZSXTS<-OH>>$S?{+mMz zzd06h4jt6#U$DVEjT0yibO&@5Y9p8-NL34^A}NbJObq2{O-W8l437(si;ckpp%A5_ z{er?p1tZm>=OajR(W6sSkIv5W&piRe$YahEM!mW1AUm84-SP&A5!g2(LxA$u`G3ea zVJ<3^qnC3idz=9%7)f;oP}!|OC`bB3na9?6p&Uet9C_KbH(qhYr(Bz^8Qiyb>gqN7 zHnUd_ZQpt1%6;tLmG2EG?VCnNrq>Rw+k%lSpZ^Ef)RI3gB+%HET(0*x3D8JnN^F}J zAc6Aawq6fi@aR4#0gCk@H-QBsx|@&a9Lrr|Ot;KRKJR)P~wmOl4*{Bjx5 zk{|{l!c2iv8y?Nm!Q`QZV;hzS*dO$I&%#2vNh;7}Vw(LMiZ)Q05eu70_Kv{&i(Ulv zW}?AB&^II-lHy`QBSNWg3@|;?65@5p5b;e#E-MgT64<_A9oXY8<)=%n#qZ$3b#`u} zcR{csA@l#x?f`BiW6$3xY;Y^Ai3W{E9|87BM)FhNPk6P%sg8_rMwqW9gqrGU$y642 znLu_BL^0pYCE5`EAeSe}rdqw#)-Zdgq+okEQqnCZj3tRUXUy4G+N^M)_uaxp*Zhtz zy7HxEI%|uMjb6?kUkV9440ZG8;Gq|Y5M<^5NV^#@iNTHrR|)V5jP!qZUHdn>ot?Ruh{?6SLQ?bVCXD71vv{ zN8Ec?t(~2j`1#JYAw`q5pH-j;7XBfd$7|OWW{h{W_uSUK233yc=Wm8zJ5i?wewG=q z!~X@oJQ^czaMvco5{%Of>$jAFdYT)k)x91QBS|sFm^9?65Ka=IJ)O z$4Tt_)7(xC5bxci?)}Sm>^yRGFKcrgIpX++|Mn*;3m%lHtcj>BsOX6FYacg4)KwyG zgwAOA>CpKE^7Rm%@#^&u`6+%K+5zb0Y5pLpXeW39ACE93SBmHP*$;gM@d9{n3ZC(u zd&E14>+sv;*9DcQ_JYc%sC7-mb!soJhXh=we#P}@^*X00G;ZzRc)uYU#PegE#q;|$ zVf>Wo$8}js!n!^Af4y$meBJi!fpm4T>sE$(SHF=%0q+y@H0~C_bx@#o_;wt9bc z>8{~-f?w}m?_>!Fx(mk(3dSlb@Ht)~%Qd^(+xOM=-7>xBmZ6xE+=9rK%hxscDwdMT z>gtKY!innY$r4a{;y0q?5RlXuNE3*JK-3kfKp`(8bed#DZm}{2L_);I#-_w3QzV3$ zJf$q86T+5WG7th)jiV+ffAjjlq$@``cVAs|Q~iCcp}ytGhyS^?zF7`waOYI640Gk> z=PS+Y3!N{3*TR_Ck1?iz2nZADA;Iv#&(9YDff_tNbh7pAmx@^q0XB&zUzSX|Wq1cg zu6E+8sK_+0dRS1KN9Db+UssD`Dh6FjY#SDe`~IOLZb?scN8^SsR>E_A=y4FUfM|&= zEG}1h-zkS|`J7_rC&=Z{)Q5nBik8O2nVCvlF+)x$HD&_v42YS4p^sWXz&KBXAvb(& zJaH9~L{PnP6(oW}$?O+bF_5bm@=^=!AbH!BM^o%!B;Ict~^S=?4|llXoPr zN3P(z@0^^$vG_5rCoe|zz6^@~V)%{dCaR_?b}3zd#(SMAxB_OFpQMSvOfL{s0pA5| z1zNcUQ59D1sfiS(c2_K?r^T)yL{-=iIuATl`JG3Lzi_~L@C(I{K2z}*{Kr-(AK*Vl z`5SsoMTWH(;scJ9Y_?IlKZZEqaTThTJ^N0zno$*@PV|U@UgHh@Rl2La=qvyimFkzdO>!#8rUu zd(5NQ*|OHDd>EvUD|3Y<>B56Evch%Cus5VDxPq1WAdlF-YM9g2v`EBfPa`ON10q^ABlxx^tr3p~q!%lj70kxABRY?^` z90K)--xL1Wk-oG^W+3)Dr&GBD&;Id`b02;9p-O>jhyuwgKyk!+7U121(HQ=tULqQ! zz^UAZml8neAfUanb-qg8!{3{$@lY@wr*!vHKWHJb=XpQ=(HK}bjx98Lo`-_$(h#TE z^Dj7&?4MqbC?LFV{sZVTqw%~fZx8nU2FB2D+MC>;S|b%=96ta*69H*BRoW;{9Hg8= z7zCMzg|5!j2I58H6KDQu3>HcVcEM)pYBL-R@$vDg@u{)!xS;=YV+|tga4c|x0IBuh zRiG8o5n3W}_6Ye0XKu*9;mqehg^#)0k6wD|QTF9y$1Xj2QuowTx|0X@pU}PXitfbz z^}BWK>$;s&ukP9~wNs}w5hSAkxj*umZ=0hPbK>K-_&XDB2~ zlFts!e(M|?8{OpWY3`Yz#e6ffujio$Dr+4$I0-Z09e4!&EEl>%oR#D|gM;)U;~C^5 zc}k*_4Lh^Gb~SL>#D3+aAn*XA_gX#i45o{YXULeVb+TwDi=+X2-ioCMoIrgwm*Tx9 z9cel>LBo_WRGmi{@6YT`!*3DK03dyGJVV&m^Zw1rs-0J>69BKVWqyuRB(-jlM}?2ZjvroZRBv$m(J`-U4>TW?qIAmAKwR#slE zt*Ne***qM@X#dD1n8gav!lj}bOhRFycFv<3B3Dw;B(-`G>GZf02)7`@*Oy{7Y&i+-s1K4no`L4KZp14)a_D1wdyj3jq>(L;rCj)}14I$Qg3GrJT z-@+hagvEx%M57$!isg6)Q5?$xXKjToffoVdGrx7imJQ!=-p1@s=kI#Ddal3TfA-&; z`^f5zZXB(y&cQa|o9pj1*Wmin@e|nS7au=CRt`F;C??kvKN0QHq)%)d zdr`R$HC_ju_m+3GH{W_Ib9OYf_A$Nku^jTsE3YVjuPiPsc@v{7fRyqQr-Eg%gp-Bf z@1o=|7N$d{LOJByp2&$1l{dCPiqW9`Cr3^MuvM7(e<5Yai7iq1`Fi|O->}+NgQ?{ zcO|w9^xDBlQGiRqP-O%M+x?EqMAVn3|Bw?bgna4!wfP*2u}&0_PSGJh+~pkV9+`2T zIN@YksJcPV?cWv$Nmd4pS?J=kGn7)2Tec5ab|^}D9Nc-Q@R-$z9CYwWRag!a^|9#nqB$f7X+FPyxzdNzs)hS?t`bBa6U{%3lf z{z^DV!zX!}j{EN{TSJ}>Kvq&wVpX~L4PP|iflacC<+NI?-h)OHW$>VE0JDbm#GM_lp%-8tVx>bey zOX*n=>ki}~)ijWWTOmpQ*JyAqt61Z?4*5UQY$Wbbf{^G9pdgn4c29vVEJYU;7MB)# zgY$RA&t1V@DGqY#GfI`Kz#J4)&KfBX1AGIJJ6{w9dh}R91EVPTiUDHW;pV~j-gCCL zlIr7^_E%rE!;gyQy#;%Nq@fh@>`H;B>QWIDYUl*#IIt;H_Yw!GR^aU;f+!3NgDALI zOiU$NW265vShbXj%%md^B~8$ne44LR>ih{V%R8jq?&y?c_!-+^hDQ=V*okOTF>S&cbmDMi5Ynq0tuB=hk2P!SNa zbd-lT4kRit#6zf31j(nJEkxUDr>)iLY_sNnrTCjKR{Ukh4iNMYcJJm&6yOSuP~ghj z=oM#NuI=vFBIKD>?`k*i|!)2gUjyN0G=rh9CF_K>Y?wI@Lt%jvbcswg*13% zU>hPRiT7sDcr+!N^}g%6W$e@9I#%Wczn-MF|1Mt{>?h6rxSl4t-IZzQ*-vVihhVoC zY$&m#mJImt+fiWij1lslzN!rs{+lYj*Wy$jaeQQ$cNIMfK1%9jh8G?xvc(?Z%Y)&Z2eIm1_!zkhdf! zPpe$n-_$wkXvu9ISr!x7ba_wrRp|+~{Vk0%75en!z4bHYB^zq{PdS2D*k3x7nsTha zurFsb)qE_*ijcf+VkEC?g0@SKah`4-^n&DelP*=Plyn_msg;nkV{v^^jU$jb9!fzq zjv#a(CPJ-ol<9iE@JByE91@IXc^na#~|VQH*r0TP93t8Vu(bvd@&x7Py&KL+%WN}f~*JvSwAI~VAU!P~cz zOkYtWAGkzs1Z*(It)K!>vx(2&PH#?n)TUsV|5OA{w zMz@YB@5^s1kCar;&L|&a@KQXukFNk%FD^m^M-~`7%Icmjx7+<9MA9fi#BwnaBILG3 zF6Q!An35}3OuGw*Q7H8WtR;Hz-Si$1gUDEzq{!mHDpcCs65BSq;OuAl z;uVeoVfAx#)pW*4aW<+cUMPsL1B2dEvsOr z|AfMsieIvXMI-yK3=$k-VS3kULb{H0|(YfI0$0 ziG?8_6ZHl}AP-i6t``Tv2-kiuRQR%Oe0Te4d{zAw;9ZVc#*j1%1{VCBp6U-miuA<5 z2$qrvp&ohXcn}Q!En?|$LeW^XUa^Ow#)Q%qRy}ro86+K7PQG#z6b>`amfB}k;l}hK z*W!M=wo~1+r1c1s)(GetRp|P~!;p5yFCHe*ohD#={w7OOKMEp&q~IB&d<_TI9T?p@ zB7Yz9VF}3O;jDNQql%U`xtB-5shWp^GnJ!E!?-23Js^691InW@O9cIfSQ!{!6VO7e z=~5v!fvU|y_U2gL$oMEJH(UC^0?vK3_smlneLeSnv9iVi8X~CPJjNO>O+VoYu z7iR;6CATwDt_6_2v_-Kv3z~?LE)K`>Dg9j$@;rf3)%@oWvIS`V0aH)`VfV(a=XP(> zbN`i72XUP&gk%rbpek0K=ep*Lpe8c=!aO~{z`_U{)=HqF%q2Jn+L6x~@+ZOEqH&fC#@T$Jp=M`O(`*f6Ps?SK zRjz^LWYcs->tLZ)*`X;KES5v-FCje$NMopnYAOkWNYXq=;&hPCNjHJ<(|kwxd<9jc z>zpd$`nULXVY8&`a1`b3Nh{3e-<>aIKf=0SR4fjV4>W5H9uz7J|5zL~q;AProYe;| z-_vo_5^LPkG&Z$ieb$d*(Ru9U|JXV*y0b9To}S;|*4A%f2880gPjeIY#0N1}ZfDeg zMD|4Qb(({?{)T#;*0hFT!EHDMi;Xl#uT(BP0pZ<-b>?0L(6VWSN1z=3UJJ8T@puL@cmai?-vaf2()|S)1lur7WXFcop zw|87ppWzyc|-D@AxzbCG%oEW{Ib7*K^AYcvJMLrPRlL2u*!`#<-%e(^pH?_`GNAncE z89dp`*f-GnljD6j{kph#AFgj$81JL1AHMR|Tm3DyX=Ra7X$3iX{aszGiv99s`{PqJ z9X!^jApbXZMvMcKVlXz+7ABCM2%$a^i1mRmDI=kU8tcQy6ri3n7-GyQApzGCNZ#Dj zzgQK|?ZRPz7=azD?VXiHg$75aqp*APBc(5VtMu+KmVE2M-F?=ImQ;IUL3;LOm%YnY z&b{)=IgEoe2nxm#L0&E)a2UW=0(k-B3po??X#<5FasJ!G=)zj1hWHR?W<=LGdR%Z< zK=06=#j&Ixn1(QIqX$*ox)TIF!J<)>#C=4UuNw>uUr61C1?-KiRndlciiIo=4o2`3 zg29l({5T!jiN%oc+;o!2`H%=1G^mp1IM#EC*dEhG#P(>8|H$>k69xZK_}JHTpL~Lj zWcI9523ar{PDS_wt%~EiT4Bq9J`IP_!o)UQLFlif{covWpIQ&q>&~4*^}4DDhOfr% zRlTd3%xS>N1&q49gqjqbhkX;1DBaNT{EP1ebA$iVuPT11qV<_+Xu6 zaI)My>ZPx=X0R*6c`GB6Fz4 zHB%)eupvyrhQQ|U1p-!H$Oq3kMw`Op&}Xq&@+`RytKDu@%Y{Vo{LS#k zE>K|#cOo@Quz;!X>*H>(aLs?Ow_%67YHNM%ri#+3%DQspPxtq(&F#p_?Q#8iuxP{R zQ$+(#ws%cS!`i&uHFa!N!$e-zKvnfXWO#dXexupmS*nEDR~2`3-d@~k!$@%|vtL68 z?x5;tHY*$&5co-Xbhx^dD^7XU9!)wA_jE|cXmw{^s&YTXe>3wYIH3wDxt67J?Z|>$y| z!#W&225OLxTJK-ENH zx$E;|HD#j(W}~IT(UMc!lfJ5|zSnNg%3WPju{r|K8|IuSd7^ZEM9>t!iPOMu=zIp{ zG|o{hxDGF|9fS;=+ao>j3JDcs)U35Q4z9#DzyXbPJ0x$M$BW8O;p_A&d*4=piFdC6 zYafICrI(OfmCjq*L$K3pmWp|!09sch`}L20?0Vnz2lf*sho~}UMEoex3YoOI>TlSI zF=HmHzctTt{F&$Y0}hJ4h4zs&BT-HTlfm9aE<8%GDd-9}0tOaKLHbay8jF^ykiF%M zo@gB2)SCZiL!w@z$x z_ID0W^ACC1+(+ArD%@3N^*d)+zVh-Q)Kw^4sD1->Bl9P;D`;H0YcWn}1T~HLhUe@2 zN1O);0}qL~qc}MKQu<;54QvrY-6b(>5qyMlgggW#DLhdmi8sTT5`%hQ7^0|%1yW4vDI!b$^S9gbO8fP8vTeBv6N{3wOLv__%d0Ea@sGR^A zjHerG7y(-rc49*4(s41Wn+b_T2q`guCorTKl95}EGQmVzy>T&oUCB^E!{fd#IMw*L z*gG_&=^bBs9m8wzjEwEzW5W2-vN1sAA>ZKW1U{$HW5hPtHS;%FzDb zv)M;b7MO(&-*OkX**DDAYiFF!8GXZc*zAjn7TWBqx5H-two!`vu^+bmXPr z!1?*=A9=|o0J8Yq@zP69wvGMrRoky%&m+7~Ma?Ag!ByqiAX$dx1lNlZUFom4mq`{B zdlvG{g*>yVdeu3(7;r@$Q95(OKyHD8h`R>~hD!!l15A6*T*&@uX=%A>IoU>=4G~$0 zjNw=(Qd2>L8_V6_T*hb}g5Qv(DpvNIE=1NUxfn^{TKCjeZmEk5*}ASImlZ2NOR>~! z7|vf?R0NKOyVA3pZf)J|cJFLz*jmY4t24~mSu8Fpp`drhfU>yY$%%bjND=SZR937h zEL>9oMF$*sE~$5yE6=fKJNDNr38hoj?y1s}DK|dVz34kHfh|+OP8Xf8@(Ua*#BahD z{N@}4)ZFSB5sG&IYiGodb~zJ9D%NdrP1LpztX@?%5HsAx&xm&{4$$LaXZm2hdsTj> zvjylY&Isb(|M$;`55*br=vx95pWjcOolYs2M_Rq1it5oDM9hr6kV_B6+(|Cr7}n^p>UA4;&IK(iuSLkc>ZKhltwa1{6kL@F-GUoFl# z;Z~*c4AIcoMyEt4WB;KnHQAsRg!_qbMV}X@8m5@KZr8fClgeW-JhV982Oao;pSIAghGTpE?|rJp&!!r+qm=ON#}FV#gCRZ z3=Ci+Mg#oY*I^5~HjlGX`}5D+Up8m>LEd{N5!Jw%PN{I<*(q-%vS*1+*G^Wx+`Z`a|QnUmeeb9GV8uC6c8G4;$RIDk3gc zZBeEH)&Ee%zfZ<0(hZYu4jc;?Vb!x+Tx)AK&2lYQ>~LjMUw>CwfAnAv>l$t=?8%u} z{Xu$jbNUTw>B@_%DoUC%Z8GZs(}n?aT}3*mdlVb#pU5vKMpOj*U3@` z1qQB!gNKpQu^>(FTnnp3)VFL@KE#im@@jC9lvNgq7ogRFPQ(}~Ln$ha#V$I$;&}(}@iK4GG-T|B)q{-2;Rj^erCtJ0rEwqHxM&fOFEkUic z3gp9c|3dGAof^)QP`$vy3jc_pl~hhoEk(u^gdSN`F3n4rs;wA`b8?=EgyY%LitJ4I zHou^#tP=Emb66Yl*NGunJS>#BLU@_rlgz4;uAlVYW8@;LlR7detdWCTWbvhhTEx0j zIxDE8Tt@l(_c?!9;w))!SEt$yQA1s_nZ2PH#b0?rc1Boqc~VU;Q5TLkntzA<4L~3g z^h5bN>8q%gC(93i6>dCeaR4KGj3TC`&3tDR!}omzxDf{V1C==lu?X{T-MdaKWo>Ql&r56pSpoFTeh4T z5TCg%C)ch$*&;p(BM`qTy@S!|Bwn)%9>Ju}ttE|U9oJmwB&5aChM-~qSVVK-J1#1E zgK{)5H^JtDxrJaZaio0+T?tzU_zhC862-*Kn&AlsWXHx)-v*x^t35&&5{ryA#zMwX zn>Y)v?houqh?2_E%N!%CYbPz1^n;z#2l8qaB`3$iY-PP!b?xbnv>fMDf6=-cg=*xk zg#F{6m_Z`R$O+J-MKf8a25Eu=0wz<@WwkT`CI+M+c(?G`B}$3NT5q)mg{TP+h>Hkt z(p1w79^PNFy|eweHNmu}5w^_@S^uG&WKaBrT|F~cK2qSyM54i7o@j7c4iXI#Y_Q?v zLl_tLJ=A0K^Yki&n3P*uqrp(rG`)2qc|LG5CO zcw1NdF>8WxPs8~7U00v7vR%rPiBs(6nROLoMFo^xadq>m9@d-l4`4Upbt!NG_5P%K zcTG4bXav0GAmjYNL>NWZ8j9@7k zW;?=V7L7Do=_m>b_!Ui#T`T#yoESjp+QGVE%hJJgRzfRox1ekbsfzW z?kl!#Jz7(#{CIL`X#MoSz zDA;U#0zwHXmf9zUl17i+uwY#q_-ecP%JyK5<);JJRjjpgjaXS)W10cs=N5U4)7=cW z9V7t9vdpWN2&#+9)(fdF90Dklh|jR1H!q;NOgh}sa=54aNOSX%?z&B7Wt-~iHkFre zs?&9z92`8^DL!-EdpbJyxWy;-t)RMtpt{NK1Q1+Yl2PD80_)k1PeF6RQ2J=@;M{$3 z2FFskcL96GKy!hm@WS=dT)YKVqiHY2^?FfSKyl%cN)*?sQe2hbavlSzpOh5lC`%vh zyRyNSeqdGohT^=Mzvtwn{%2mjy|}R_InRa4oB4f3U&|9G~g+G?AA2C-R(krO0j)9Y-#F{*v|AmR}xR2-(&Ak~km*$VG z04mMJCg(XcJ@Y0l;^KL$WY?2hXW>!vNo3cx1la|D;i0>-?pdG?lT3g^9L5(+y3GYt zOp>kudVrSo6P8fM4GrtSQz|I~k}O5a20f$iH_RRr)3Ff_D>lQJ=(f*NnS+__NBAcgxu< ze|CO}yQz5g6y1gWe({^rF{(ZMF4kyKzd5*jjQZ7+-e=*M9eDEz{w$1Ei=H*&+3WbT z;0489jc-N{+&#?if@chTGpG|~Xx$C^x_~RR`f-K-J^Hlb^2Wx?dwTe1-GZ1&)n9NYu21>6`#PN&w|EK)x>2IWuc?W0XU=7N{Jq~MZnfihfl->sv`W! zjY`k=pZYK-Cr4hJgJgHeK8v??^Yaf}qCdq_xkRb~p>m0WW|7ylk7jweM9?fXS_n*H zx3Yp2RAo=DIo@LKKhwFbxS&Y^$pVj9(VJD>*<~Eh>n$jns0WLP5m45gR?a7ZM_fR& zJbTv5Bl0|@9&ley2d+l|0V#^RGSoYkW7Rp2| z7(^!30xFkaOy}bdF$`ZBhYOIZOFomYe2TS|q99Oy-e)}g;r$TDnbeJW_wadNfInP3 z--{zu&z&sU)!TiIBhk9QZE||oRiC%A!^#7RyV=cK)|9O&$xpXivWJ`6dR6{#n4iB- zLj#yi{9y$6Ljw-1gr)hzz*OidDTw^dPRX)a@OY91{2`C)rsK=UAI69nITGYJhe*tj z%_|*Dx z2k?pwfYLtfl?2%QoWv_8tXPs)wEB2O7$og>yVIU;x1=Naz5nq7&SgQxl=HERvaP%B z@W7Qfb+x}_o5?QLl61x@S1T-ISO>WUk%(!S)|i+M(#YSOds0$1E_ z2{f?^rjfL2eEk=4jjB}egGFoIz;U34ueDfr`RRpgEieEp&evM3I~H4|(EsE7W9Xr# zrbAs_{Ihntv~;>weCj$*4h@~`5T7~jy&WBU-Qv?rp9evJt!@*T4_?(7J_nF(N!Aur z^!(KM>>oj$*}0s#pW$7rF}ZOMKj|;S|5x})lHfhWStpMx%UOB3ea%qQ28+G#p61OZ z`L)V9FqHquZAdR}OwUN3%xrh&_ZLtWt`=|*t^Wu7q>nteO>Od#XWNqG8RM!2A9cz9 z6h9eAq&@s3h&0jYn?a-h_xQ>4l4;ixWEvcvhfd47-vO0qe7Awp{r~fmK62;JPkQK` zta}^15zV+6G-Hg@j3#$|AkFY<5q!jB0iXSUMmEMShHL_fqlBQ*;l!FYnLqo#`0M9)6E4x|f4{ z$EZK>y$i?bz&lUy=QN9+Gvc}H_;Xru4;-&&4i4Nq%)MTIBC&P7oaSAaPQnrh7gAzi3Md9vK|--gLcDDC|j;w8gDim5b9 z5KHSG=qenEHN>pStFCcZ+m1tBTX5oicVSUoN?b}zQjWuslX#OUfocGz0$!&uZa;3A zOYdH}eZvK~VHATwJq*&8vQy)>_kWW0;!~FK9Xoa;vMA**6B9(m(gBw}cz?V!`Z!ls zP`D>yUD6V=djL2Vi}vES7aYi|N%~ao8nhHRieadNIyD9M<)V?8h?rIR)$YdLEpZ>e z8qxAzWl2%307kkaZp@3C1`$RxG9P8 z@v5vI<|)yAf%|;iQ9LiGEx`)Me2gEuM&hVFqZfNfu+xH@j`3+0p^(e#U)?Y=v1eUU zQB;`C*)^_N*;QCjT~k`rm{gwekNV=$#x0ZUXP1YhMUM6w5^Pzo6=Y--6gpxvlQ3)t zmg59gB3SBtOsFT?9uCJs#0XvM4#utayPxFKD>;Lea6`#(T+0YeUHVcCfn zvwoob)xz#fP!6)oI(J}+jezNOfT_^Ijy|rcU_CfMRtulQwa@uW-4f@!EpWhJQ_EvX zAeJnlskI43Cy6A{9{n}fZ;m+E_4iFUbL@_x_Ksmk`pdNy74`KM<#kWDx7XFSwbuQx z$X4KX7ubqctjIIvl~?2$^H!8)W|++x4zu!3E@FFg?ddtd90z83*oQgcz!;yFR?Ico zA7cPnGLEPw5kA=q+z-SYV=B)mD=^1Tk>{t)B|PGo!{^QSu)!SnQR$ZaETkHixXb; z#X`43=2cm!0-YuRGj8FsGE&(3$Tz+8;>b68TZ(>T zhO=B^hd1g1`i)0;fHQ`Q7be_QBI8NZ7)@!U1z?jHUu zbf-()tp|qDv!ohZ_-rbkJw3kB_t749XH3$pPhbz>TccBCxP;UF`8f?$^;>v|Me2jYdz0tgE7n{XWT()vknC9lhX);&jyd4f!dj;^51oS@l1ZJ?o6=pZ7z}@kI}sO{ggVLyH6qmq zTZMm;BtQ=r`AGuP@$nIcxcG=S;|-BEtZ4?{QcOHNMDH- z!k4o7bJ8hznA!DYpnD2|p7^F6i#_?2dN!5K&q}AHZt%B&DTSm`1t{7?12k*W+AxVM zF`?4R7(RSqnlr1m-o0@;t=(b_GsGFX<0C?1La)9~8y#+l6TQpnc(aN3YR2ae@g9vn zhuwm2S?b5=?gKVyU|UFcThR{`JM!hYhkZhoCrSC2=A}OQ`47bA#+PygdzF+<@^XLPj{y_HZ0n#q=;x7!(I3!|D3v zNW53u78<@%6BHCY#>)gX@B~D4GAOx?F7TUDaL|Uu&yian8p7jSl2TD52HjAkV=PAK z&3zRS5t5wAC={0>b47G6Md(s^t_aa3{(dg$Q=f@Tk9#dXJ?`{PNjKaa=ZJqDzfV8) zdg5#B#b2jCi~rI+Uswf>HCVUhQapHH0v2IHJLe}nNbJ7^y+Gm!{AuG+p)k$>B$lOc z#NNFL4Qrxgr}Gzl3LwHm8L$Mj#HwEbu?)PBP`ehs7D@Ibn>E3bt`CXJtdc$4imJjR zl{&@W1uXxclkCRq%+cPubrx&y9UBfL+Ks8ExPoOWM*OupD>=O~x4hK>&E8mNMs;>_ zlC99}LlPNUqzT!?al|em517Ra6yfd_5MnS=RC`CN;z{yJ>%Suc`( zF{3pvy~2@ft;$U=&q%UXWEVE|!s3mc)yn>jC2%_I1SmCnr*s#Al&(u;xe@1$*u7G% z=e(h<)0(5TVVfv|I$-AqPzOjrfz)CC_n?InoEAp7L#Yd>5ZG?N^9wZ%9?#LV3K}TQ zzs~+fBa+e{(NEQrf&_!;r?G)9OE7HngYOZ&RG$x59`-k7g$4IMiQ+?$n5g(xl_Hu$a0tV(fNzK7%jo`cLNz0B@_j>#U<1s^$yJ={g)kxAFFL(=Q)NB^}x zz7QK59uXTG!4AcR6ZHs>!yL~GNqAFmP9Ol1Mbf*{6|C@+`}IFfP~t8;9Dnb|@~`k% zTCgfuhAFx+g(`ZIF9eVqbP!_YG!z%SmRmiUgZPGQV(msV{%6;RBxD-kIgAvqQ7+II z6M}eA+ScX7H~$?S@$IPS=&1JOw1e4ZbN0cs^GF>e4lFV-9 zFEol+j6&w405u3loVIWWLqwy*&6`wG4#QA=urLga2pM&a85j&X88Hk2L-7oO8;{fx z(FmDr&bl-$xg#`!LfbLnSk&lXnVpPfpE{x>>#*&D!1WCfPkXt5r z=NhsmH}UN4;aboj`Ylj5=O` z(W_b7ab$t>;P&TOyyTfoO;6^zLA+%LMq$8PmP?UtxKv7b0Rto29)+apJlX*kGM7Fg zJl6a`d3kZ(>}=kKKb|=8=b@*S50np{CYa`796!MuP{WJU>3XE<<*&fokT{c{*0Ee+ zXLoUrwV=GRqN1|glJfGBXV$%Y;>5e_hW>ow#Ggj61DfZLFqg)G9Z(TcCw4#`b^z`e zac2d;Q@P;IQQWceJ5>wr;2G%|ey4iDodb9#iQjQAxKoB_Ugvje{O@3%lJv3WU6u;0 zjXNJjp`4tWchAMKR4YLSPq4pz@)JaV)F<*;w|tWAy-*+I1(Poi&)viJS^*c&sAN67 z2+(Jh^bxCO=1+P<6=lk0X7ZT#JR$3WFH8KESSRMa3v1FW^@%m<6KjGym>=C~S#YPB zwXznr4|m!_0-q_w>2N1|8r)bP#;d{Sy}VZkuHV3~6P?!R`{_F7gZ9#Y;<}EgD|c81 ze;r884XAtqCL5dvnCu(!PoK_by0d2y{!6;FR_T5IH1-Hs84!Q}$BDz_{ylIgGZ0r< zT#T$DL5|a>^KQ7I^&5Bz-~rB***X7+%t9F1h$P}9%8;+b2D~@v5n+JqL*DBl%r3u& z>u{^X^v7o&~`SkccS;;wEx~@tJ(MV(%$;7#857?o73d}7_hcrYLc$L zPP{7wXXbn0hASTv%yQeq`C$B3&>K0Q1mPpmZJ-GTvd~DoiZ&iDnlqY|_*Xb~`*G3o zd;V-R@AH3+L$8+ZVE3cosZHM((g*3dPkIFS6i(+KVH$Ei@^7q#3oUQsE&F-P{`0n^ zcgiw~+tND&uomx-{?6~<9lkr9PbW#hg}oD|(o{~9b9`bzzP}g{yjToaS4v_)EN4Jv zmvY6|`F5WKV?V2F)1B$qC; z$;ENu;W*;M3Tv#v=NHtyHOnsN*Vpsw>*+e*TeH$TxPIZi z73|$xM{};cGKaGs#GHtf5)>*gNwyl0_OPnd$k)8(*-#`oUc13DZuGNuWmSVOx zmK*boF{#epQcZb@3I(~-}IpOIgV z;S(mcvLSHxE?^QV|1|gnXOHV8^H(ve)P?KYp*qv(w@Wwk>t$>+Xd$)V%G+;MX<-T5 zjzU7kxW0{F-{zr(Y%^$~3v>wU9vVOkb5J7LA_M9CgkT>r)acVJ3GC}JVG;3hh82de zo%?`A=ta5~y{sfoD#-gbP6zY2!!Qa}Mr+jl@D_Vn$x^atoyx$~ z1woe>rk`b{vgO;VJSAI5oWPzbp1)eYRZ{@o)-HU_v>LN1J`Ncx$shvN0c2ziQjY+Z zh+u-xIvCPG?|Y0IjB%7P!4O4^v4u;O6q%8ObT!yeWNdVDk2A2Yo(k5otv@aMNSnqw zc4Kevjbm0#`{C@gzAJS)<=MtdJ3B9J%;+=bId|<@w&vFH@mtp{+p{Y_&)7Tn+20-O zTP9B}>pRwkvFFcU4gcyZq$Jpo_aMkJ7D+uQr6htW(wH`t=`aB;a!Zo&GHvlR+I)^- z2&|y&my2fV*QDKJ(@m0+q9YO_qWNTtXtq??$2VD|H9&NfJ;El+`>?X?;wa2snVPDB3+~?3Q|;5K$;4I*c&S9`$9JVGxzR>fFQo#|GnS;M-OxN-nnyU z&di)SbJ~O%^u7@>+_2$d?(qE9!*a#p!?>6TU%i1d1lN^id!Qg{9l% z3~zrQ&WewW2sLQc0dU~5QCitrb41Bhp3KufkckL*VW7m+Ohv44`AnlMbNmZ*nABdK zBuQYhMAo-Y&IQ{6gfIwJXjz$)_wzT4Dxgh z((%B8D`X_*{&w}(scPg-BlbqlyjOk-#b*JacejLFXD3NhQn{p2;q#%^670c8rWG3OW|v& z18=}$gZq2BIoOJv729A2sBqj8&9}FM=!E29Fhiumv!GFgtH5&01SuRTjgAZs^7S?d zOnM^6;9{aT1EjT53z^K8dDozko@^s3NCD~}+D>xA^_o7a{`yWaF_roOs=n&LKy_dF zca^@Ms*gr*A}#y1%;=wi|8YTWs{;F|`}qf`{rju?23EzyRt5G|_fx6W{(aSb0z1d1 z^-fRkm7d;*{SO)oi}IOVB<5-$Jf@7Kbw+NiLG9=30Fg6~lT?GL5|N_WsVVYg#j%CZ zcnJc!9XK$bX^T-D9v*HCH@c%Y0WLN+eqrV_=P_szbKlch8p>?iT1GNEf?giXlG_YB ztSZu2SFCS6DbQ0lETdJYwEF(~G5I5mnrmR|7SuDE7AN7GkmZA{}NoC^OX z>@z}1Bq`5m>nd{ARcLi7^@xd!WD+GdTip6SF#&_}5W+co zMC;so^_O49o~}+ztWHal{{5NWGud*_Ya5o0NzX#8(&Fc`vr<3CsWJ^~q(~2lwT5^P zNz=8AXHvo9^GC=dqey8+k*^N|%0d)^h-(E(ia>wTi3-+m@T1h0xx3(ih-wH&@(b-K zPL0B7H9O@+k;t&nkYFkNnj8MLhxk>VoQSz%KugdpCVxc)8<_-aL9NgdX8DT*e%9Qm z_L|5^g%t~Xx(px4^8<&w^jug`I4M$7KBe{83K!^mUw~KZFw;ve6=PdVlQL&?sn*BG z*IQdUI)@SEq}oiar@z0aHj|x2LmM|Ey5xs!*MsvTTvTA<11M2CP@dXD)+2yKvF!;2 z`%0yBUPKNg&}{KTtUS`HDY9HKlSr$H0@*MX;i>b)EOIzvEz!U;7NMG4tqjqgFPbhzPS?(vqvhaKtSv7ZivJDZ3?$3LJZFc1t$P)8J!<;KXoE z##X#Y>yD+`RdS$2$O@q8No#eGbb4%J({ep9CUQz^g}&=c?b^N6Rj(|b6sZm$kY!~x zsEGdzSh2gk+_menZZ=r4qqO0MhNT%BrDaj~=93G-FEeAztfsJH`fN@YuuyQMuqM{bG+01bOnO4=9>F#idoX3RkE8f0-VXi0RP%DbqSQNAVZQw z4w3O7dNsOJSDKb#dfU_3ubH<-lavReKP22p=mz@rlDdZu_y4UA^91t+*~=# zm;xZr1|vivLgOQ_NRI4y7L3ShATeL6i6m{EAn;I;=pdto z%t{a|IHH`huoH(x7J$Fc-K2&A8#Q^5g*R*w7&43BXp>aJr2jJRV z%Q4*R(7s@0c6UP@6-iZU!&47e=u2@TJzSxoU4vYPhr*3gOdKrBj3btj23=E#w^s5X zg+2^k@bQW942tCVOTMkfFdSF_gG+p`+7ZwLD z>a$3<*!26#g{SN{_$hNby?n0PmQ>L|{p`lTsSn?SPv}!iIy*;JD zt#x*Da$&93VUNailrrtJv>dtw4^N{8q04kS!GLj|5U)TeYn<=rGfEUkP4bylcUo%x z{kmB`lNz?sm7e|Eou)e#rjB&8Vqt$zQ;*Y}=;!V2tI039L&s?PXcm{`8{^k{_3`pM z8gHxv^<%Sj723zn8e?(ll+qbvidqaW3^&%5EOf0D3|g82(6?IR*CMQ+3&Ei@?9Ud; zki=$-2WrmL#Jn^KaA49=X&HGibMJQmnA-_xau7mj#cR0bUljOG7%o<{*^{!>H)zR{K_f&&in!=Cu6I(4`eg4Te+vV)|dq1XrZo*%6T(CA16m}OYGnP#wba&t%*I74B40* zmD#i)=n+GflXVkKle+pbdS;peS@ ziLoXaz34De%oZ&t?V^jAfF6Qq7+N~(+HqkGmmWOe!X6m8Bib2SZNm@1KNu!Z?<26a zM7tPOWanZsg2x_7tPZU118BG{UFfRsM@B*;gz+f!!1n_rn#1_)7!pT@WYl8W z4h-~m6dCd9<>BMz3?9SFQ)DQ>n6j02N}aD~@l|Ao?hPm#7gnChA0kPRS^esPja^e;v4bTi1kY(;w9d zUAL_c98=#l0sgh!VymgLYh2g*hNZJxnf9y?`jLLC->9eG{S>sZ;oq}!xXd|uPa#)~ zY-zP`n)Wl21)_E0#7XoLEU7qV7vjPEnBCx$j=-VdY2^v~kn3aT8lsaVh7U)a%7d7sfkx?wmJ$mPmc6ZvssQ{RHO+B09x13o0PcO^ark zQzT*#NMS{AbMcpE1LK<_v$@ zbwk{t#+vt2H~&hZEk$Q z_FK&**VGOgac2)tr;Du4Z%aQsoatRps8$gg%yWF$yOv9QHlJKU`@cZ#lti0>rCr`@ zKvczZEYuRy5OX|p(3cg&_!#?7#s|a67omqj86HU`+SUUUe~2t33MCK|D+hwxp*R=; z9HTW<-wF{i_ZdNh*E_z12siju?3)pc%u2=?aZ~?3t0XVNN1Cy?Y;nNs)-+iIX&@s4 zi`%Q$UDF?V5qU#r_qt21*#$j(D0IRE)EECqeNUF5&N{@IQtZXbkDhcFHjLA?uFl9gLPkLyD54QL+ugs!VTz zVRrvf`w&kiTs41}ZP}!6W1}XGw`q~vA{Q=f5Tl$=>WlIGt4j?#~VZ8J;X_9>>eQkQ! z!GmFk4w*Kx7G#o*^iR$ctE`cV@+d)^3dF7wSXdXt)TQEhVq>MSvcdjRX>Dt*w8cJ? z@o&s#!Vagr@?vke87Qo+6uk+aT33KCv#qwLw$`?mm{@5|CH}Ppk)u`!2@M|TX=P;m zFIEVFwN#RGhtUViRRn~wI`E5Vy_ZX%)+NvZfwoAHL2`^%!bOU)hmyn(RuBrE;;Bz) zO|-^O@2QVKw17ip+>8 z45#_ppQ6**#aoGa$mi!>;$D)JpV%eA->1mk)DUqaEW9W@G2%uD5>|s4@(SOR^C13^ z*(MXnZsIjQHj(aV8NfqzfEi03sxk((rKRq#2$Zo)jCc3R8v3 zKK()ZAX`M$dl>V{z)mZZW$+lI>^Mjx!5$Q6@yH&1YO_p4p=9#DPdmiKbcipj3Gqv4 zmmCj|{)+hIb_sqVHD#vJc~O3TQF*Z$<>eXl_p-8#*nD;)KbG@Ah={oIn4;XsbVGDG zE~5?Uk-0^t*Md?bBU6LgwrJ6|U5gfNdpJzz#2&BujR+gl8tn7=v&R6VW`6rsE?dFGaHef*mYjNS2SdVW&yBx@lu0W15aY z_A_+~XgMYBha9$)ee<2DE5V z-@4rxcg2DUZF8(!Rn~TiF?LGs(yw0!?sWNvVH=L(IP}b(31tO=5ewJ4hkGTIb9S-Z7JdxL+M8R>jUk-%3&-ym_) z!NGohjNJ=12FFH*Gs|GD+UZf&&*3r7nwe2H_g`zy7Mkr0SO5m&un13IE1ht`$l=a-e|<&~QjTJCGvPj(w48toP8 z9%*k&Qb_t{^hSg2s0}#2I!7uxK*R88t6l9~cw1Y(+S%GkiD8UA1U?^UX?;dvMnP^) zT+6r?DM^2W3Ag_M6Y6Grk3ci6Vpy@zt$%rR{mRc^JiL}2UuL+45(t}7V z`ZZ$5TM_pR7injXogd>9$uJjc?;b{(;14@*Px|$y@QsI$(Xc_Lt+Zlr%oL`En*%$X zFX_8@+swi0%{viK`gYJS0l%Eod>*>;)GwM}pc1`}{B1}(bAHJ?;I9)J&et3*VXU3B zs)47*f@v_)FQ>!~HtnSC2FFsTV_QO)Ma_$(JH11XKKwQ`E|T9#BE4g{tvPu&0DhkX zZ-wd%ch$eX!wAPX0aSJR=vB@5bp`;ApQOIW1GY7#Y~+VGnjTT zvDgd~NgcgP-;%zjWV||bqy{KV6Ft9TjntyGyICW3Xf6BJ5K_ij(f5!a5$3SjkRk{+ z85&5WlC2LA3-Y7{!N;6fd-V{f7_fWLa8{P#M<=xy`D0OoO4Puf`JH1k55obbzTu}Z?iwr#bWTEhPy-5_ z3k>IML(d}5QigSNMGQu+a*ANJp~(XYAw_=aMyt-y9A z&XCnGB(fpnPBSrLHUYR`Q^gP&om-?7?+!wHzUG|Gy}@Kc%|;%K$p13(x-=hoZOBxN zBop4wEZiLejT=OL($au`Iob=j)$&lW`LNyN?0|lje<9zNGI2{8KjQ4^Si4L>oX*hK;{b>4u z^Mf_>kJJUL-ospwdhJ&*E}G>O6AemBZt8mb_7|2{KF1$hEn|o;eVb#JyX-w2FhlX4 zFUPwYl%6qX&zxPmX3c6C#fFgCyQ?*7T)QO=Iaf z(^!=MFt3t?WCXDl8Y?j4FTngu`~_pG5v>kxN=iWqlra7R?2ASZ@v&S|57RMLnvS+G zonYglg2X`%@EJq;HzbbJaVi(5=)OpJXpRtdmG| z8EQ<&e0&zQML=kz;Gy6L#`D;giq1Tv#I71Wc0N*#M{nH7z7s%mOH+?C=i8voPEgF-5PW=POngrKb~;UX)(gDjQ{`>Vxz;*C4&# z9?EBV?*QH_UOv`{Ex^+L7WyA7ywIdIj|bLVZ;EUWnKLiN!M>FE8aWY0~DmyGEm3y=P%;fQN zl4IS|aH(0h0deShlI_l7joCick4=#hXZ z?=5>m5XzG{#7>_I9wz|1t2oAeM@DE>Q22P+QX8Iw;Q@=VjC}`FeVZdJgC%9g6R?7b z3pa)s!gV67twn>hIb?W9#ExImc}l7bPo^_gLd}d_l{sRpOCmBeo`maF8#W?8t1hKo z4;mNLsVJ{3ukyUsDa0+f&XlZ;Eo+eym(=pp)}MB6Rolj%xAjWNA81rDB}nI(BG z;>zN#r?s=M$rwogl^!3R9TIL#j%xU=>YIdi#`GeLZBKYQ-Ic7wbs1_jmd$bmcHm&} zhGEW>966F>NFdx0y~AAPT1%QdXWgyN^@FRaP57ZOx`vr{q1Zy9RCW<4;$K~2TJFph}3*hXs}MBhdq=BOoW)lljMrXjD*ZX#2}jr zneoNU-sCN(hbK&i6w)X%1j*Q9vJ+2wBqms`?NgaluX0uOFt#cVNsUX*kBtt_99R?G zL*=S2izsOuoMudEZLEn4O^f#l42ug%kGD10|B~(Ek`U6W)ZlOM(2`DhLZJhziQ|2=Hzf&39c_Q?ssX=e6CtukY$#v8Yq0m)dt& zR9U&G!_aty>8p+p(s`>?VI{G#C1EO+S6~pD*&H8H&_vZq80n&AW|P>}AxbOO^6}Ue z;jCM>UB_x>t+cAOgIW-#q0F{`t-lpWU|Yr}V`+3W!%K8hbYenW1hY#D3e+{jjlCH+ z4<#2e-Fcj#z|9NX6*P#@n=#F%G?4JiNFPWj=`NwCV>86O8WT|y+S$rwsNrRtubPpO zGI%g|TtveUo*mz|@0xH!rON(nXVm+{((z&C1Q$#f9;R(1*glN7DK^>-5+j@X*YnDXy*p zty>ekhJVg*(Dk4}^Ug_V0!>rsIIXFMR+w&3KXCA?aYj%wYLkG-F9R@7nSLip>X`^O zL_q^s9A+lwLxK^BWf3Ftp&9}&$T9HQD0F!!6_UhXO*9PDYg1lmXkJ)oen@Cus4*`$ zE-p7Wj?Ti5u)L5EJje@G=Nsel^5WvK0gxmmWDBT~C31kpJ4|+n zRWaxNCmwCi=vnD)_P%M|_oj8MH{g#%H*-f}2@pge^$zm!hPnk!WfPSNRv>NwU9Ui2 zNC%Ek4&o!72HAZ>LPc;*OyiP>{f?`&u%Y!ziDpYvtZ7fK)r9I~_n2^B&O&L-r!sS3oEH>!nMv_@B-Rr@W~lya8@5 zO=Y<<4~B=jmSz?LvRN6&2!>L;S zDsT--z~_N7`a#-SoKX-KfOC-$MhUuKpqj~c#slf1f{09X0M=3gl>=A~$#hnLB23_f zQh$F!{3HA$AfrSdxEORn*4VH$Cxh-9E&MZ^iO^kR<6$2l%hzFB1gFXN`}+3X*SFuk zJ`K|p*1;277fg*zPT0`-H-&A~n4Fxk(KZUcdR=w5_0`quyIBcuSUbcfv`A=KK>I1) zbg+(!PmH#+JBnx=0<-E721&Dr`GSP7bCld*QHCfn)@T$+JGD|k)MG`gc`IvBGNlp* zh>-gcHMH0~ui@br!t9<6i2LLds8%sAcgyKdL3Y^EkjG{gEa`NVO>#=iiU%%j_)fP^yH z;@7<@N-!|rZpz92-HDB3`Hx^TpirD8l20eMoB4E_`>Qv2URT26{u{~C{Z6oEW`V~d zuyr*Or5#mRE16P>;J`$iT6^%oFlx7jdEjPX>Xx}rFR6d(t@Cu z4C3%cWrij&kRA4A#1v--3WNr`>D;t_KB$!oj7eM_u(L!E9`rK07NT|5-RxMM$HxNq z*a+bKUo?*BUq9Lys~%fHe`+=ylADhK?#+d>OkL>O5o0D#G9j?hjNDw)TaOG2-{wOB zy%36htSh`-{4v5~nLSZ>n9k3~+tb5_alif&_sbYYiT#b1;$us;!!e{NG*qPujSY=a zg{XpYvN;@1zyh0vv1S)Q$)-qYVSrhi%sg-t15EF=Z9OcZB08%iIH0g)YHmv220wd) zv$nnISgfxhGd41$Bz;d_LYoZhvQm9kXiVGi&{S=FW~wnXJ7jZYrfo@F1-&00=p7v# z7^l`6OwO$ihO`WhP6Pcnb>c=hmT)IPQ$0znG|~;|SqyeW0l~M<+M=+R@$3$T^UlqlM?UsE6VH3|UQO>Cn?J{)_dB>Tru*C(v@S?$8;sOh(=c_P z5T8X>#x?Cpbf&jpJiz?@xG{1Hzk1YJ{-zJU3B?oYY=qL*ig`?qXLF3=B=zb>dz=i( z6L0C8k4H~npP@!iohc=Mb^>>a*`1)wYI8fleMn_8JfKX52b8%S&j&uEOdi(Q*Ya1g zB2nThywRC&fm$-Py#jS@R^lUHl%LC(vwV>=)#Lqb(ifFPFSWx*9v9}FIhJ1**1=4T zWT_f=t(7kj{q42VMjUz6|+!x z>6{_fI$o)RqjKT`6cbxl$6D5*1M4}~+XHz6@EmQ%GfaS4&(+F3m%~*x_C%c_{gkk% zV_#qnWnd~9S0yRssHBvG%bbf=I=w|6iOhK)8$d}7Zd&rlxt$M>>11y%exU-v-+7kwvVVr zfOlKf^OkD-;;Ri#W>s-+~z&Og{`Zo6B(=m<^*#+}8OrEctg%Zlo8Ksn;%aqVO zU#0on*Gxg==a0^33JNo`u{X}$<)0m)1?L87Oqn#ubdB52qDh%49;=)2iFK1HBCDJH zT&Cj9>&B?`!{?9G9kX#NSJ@cCI}#@XMhekm)Ut!U6ReS$57{HfmIO*(`x3p81w~B` z#7fiIv;c3lzr91KdvJ6SH^ev6)6dn<&Mr)!sV7`R17>RSe@M|h8THULG^F*82}pmTKHwr_ELuCfI?W0;aQug%gqHvn2S_$!xgJl-o8vz1fsrl9iPd z91|0avUhUu@PPZALsL&N6trhF65D@DO5v<)W;rdP3NdGx!>;K&N4T1~TT!*zn|s;W z&e7G)!N!a4jy)W9ySEx!a;s2dGc{)+@nkbBc{xPMv@}F*DVj>6C&-|fFiK~7$6lv# z(|IMt+jBNSDvib=2(HVhjcIpdzUj}#dH;Nc6`FAjrjrtv;O=;cF$9c3041quY~7eA zmH0p6NSc@5+}hGK!rk40ePlks0oMoRgxz94!H}Reru$NB>`8>wuUc8>FU!Lpi;E_C zI9tR}K+{7nXVVvM);=mXJ4ZLCLGJc09)<0A7dr<$~QtSEUh_M z2$;zRv?S0PJ<()?9nl&HA*!b=_mVxX*yaaA)GRYJ%~w142B~0;53e();E2@7tKNR@ zuygXONaMq_K@q4yY@@;S4J>S(EZiPyfDj$_kU>2%99Wa3=yf=WH-rW1U}Ko!7L=I@ zW#tXNsc+J3udzWYXN{eMHtK3jNM)KwLau)REOEWr=*VeIXzbaz^*>+zgrLV)zd)KE z8JSK4xwh$NTgC@PMh3;V#1}Ffs~USZPWUHujX5aEbZt@~-gKoK8y88spl8Ock|?4p zoopIU&p{DHA6%yE8^4E6^B+>v2bX#G2j9{6n6;(pEcIzT4*d?(ih+xQegwKx)7ekG zKBLL61iV6M!>5gh8;^5skmKnbBoC8^-a)3EAg)bAU&hDtPK_m4e;FSSd9%!`J%fjr z_SX;j-A86iJ*Gu-{+)MvBL9xB7W3DLf${HTG4}V2e}(@9PF>o)7#t9I`9su&u|il! zs*t<$Be@yx`Lx`b$laUY*{p1~x;!yAu(|_gHuvGf_RN@I&22-PIDFpu5e{Ga@@{+? z9KO^l_GLHLDk`^%qE&9JRrqoWcdyZ&?}y%cOs6Qt_cHVOd_Oavf6v?|CS_#!YHCxn zk|lhhC8k zI;(+&Q)eX{1&+X}TX00~%TaHnu?mjmzJ+VQ+t?HJwI@Lt&*s?K$v=OgS9+Qr5s{8q z9BtFNyYk|ml*PtNORk}@4_^!Zw6bvbe}gw=$L&1TYWDB=(uODTr91&&dI4AiUnyaY z4f;n$7WZ(5XGyskJ+S0%n&D3xhsZ6@a7*r|k3&#;UWMFQm}gIp2F9ja6NTrqWX@HZ z$*lG$`I)tsDGsZ>C3h1wdt&VweQKWjscl8CPvw3BkGm1;uI^dp}D4XYw} z?!h6nt24$;(KE&kWcjAfKmW`|4c^$sJ#T8@t`R9ll4JzIl-ZC~;9pA|)JK;QvnLPt zJUk%17@(99;ItaB8at8f47=uBR^e_R>`3iUm>fN!m{lpbzs#(c z%zJ?c)0x(V7rAOZecU{p?A-#dMg^7Uc?PDdd{s`qIJZEOoE1aT%lH9UR!Cwzv*%L2 zIg)9!b4u`m<_&s;6BuM>kXL$mI50+;N%CZd*^)Ax?dC?C{*>g1h%eNHGkof4IJ2<+ ztT@JlOZ;~c_Eom9faOV$Y)u785i?FAM9oCa2rg|7L@J4h*hFE1w4&^>vE1oCsD&i; zdgA+8Pk0}@D7K7#l|3hhy^i-KX%2S~>l8Fh@;b$Y{mh_?Qb^xXktUhHd8K-%wl;o3 z!UPWL-;N#sQii-zv1(6c!lhXjv_Tvbe%pIYq~#?FV6B@7r5-t2T6Chm7f zI4Q|{p$CeTw0m4W!;<&I`z)%7^bp1!c=lHZ27KSVqm>O?9!jhbrr(nZKzt~yoAhq- z4_~UL;elLVM@Kt5UoS%hOMT$N!RjYT5x5mkr4{I{hxDCWp=IwZf#krQyM>?+psI*M z1ir%A>IqT9Z_#H@)kIuw9;Rc0QVbKz~1OppCDSqpQ7_ix%-|Fhe=h zP;N|Pyjec}|85@5aC38X1Pm~z+!%R3>gZ-kS&Par1KLY6&Lo@+z#1fXmi@zVC<#ra ziDA}6Rc6>eVPJmJrcF$*gkD^AhYk`C)&*m#P?AJu$VDQ+#>0UnMKAPZ%-s`&lkyn@ z%|{*L8?!wi3v;Y9t;r6I2nvb_%#LGn$&l#i5Si2I2uWNUNfI}*aJTO1Bkq5T4SM?U zbl1t=4YEazRO#RSo$Z}%o!vEo8h3wZI~Vy9vN=~r2c0S^-rd!~LF*S0k3Q)P*;$b! zJA?dMjdlR}W?$rI$gkyPg^AIqsSA3xP?8zpGzV<_m=6SKi?pXIE7XT~naWJCJO1Znsw2?LL`%V%~R*d&;I- z(`WK7m!#7}q)yU9K68i6RFPRhwrZ#$AHcJroa;a~@d?mCR{AuHtBfa1xA~+9*2}n= z^!PuMPclE$Wft)yB@4J}S}V!*p1xal#WJzLtZr$pjr~XMY6_iA$va#*4TGfJosvA6{3Xwo6+J($q+L?n`b@E!}cjQ6CIn!vMldv@NO71UtzBk|5!;Pk|fRgiy z8}c1k8;zl5&<^s7+s)pR#**TW`OZ6BH4LHI*S|I2X&R}D-_3Vs;p>@@sqt`@lk0ri z+>5;8XY-vUTyNL|o<}*qusgtPC458XQsYzSFbhtIkzPp?!2B99>xnBZ;g-Tt{b@TO z^Bb<*w1zQs1Z2siW#ehC5_a)_;ggY-In8iUp(e)=u75*k)X21$>(sahZT+9_dhe7_ zJxz^>NTETrq$SrWGlh|>luTCkIPPxaKK_5cqrT9dhR~MA*p^7zx8m+*CWl5xhbFVK z5$SvuIYq+Zdz2=7)4Jd;CG{|>ksKeX#=>EP3u|^yUN@VLwojDYcTYuk3|}`QoRu5Z z*o|C3tOk)uE!d4m-bN=r_UeV`VUh2ziA2Usym{b1c=JF^j`_`2Xv+au+5WGz`me*`B>;^7&EAVoT#s?r|b)f2V_EzKaY6g^Q5Nt<46bj{SNZ`S>P^u@nKER zV;_5;an@f+n))bwUy1L}h8K>cDP;@@nR7xIs%mds>lUrBOMwAx(j+2qSdOa9V=fqTFqlbKrbGAuql z?==2cv%GQeh56*sazA-k?m+%zv%IXv!ef{d80OHB!5IB(p$V4h6dERLub}XUCjnIZ z*ta2Ei*apq^qO7l-XM(mHPClGBmbJ{=n>coqCDjwcy%gaG9Y4gOn}~EHZU{AegG_- z@D#Dz%N8?c7h?eGqe37?eiwsr)d0D!-WD%pc}Y!LI6-;3BAmP$5Cc71{~?gfYTGVTbUJ za6z~sd?)+?b6!W$S2T#3VwKoi94d|xXNa$g_Z9Yv07Z-};}ORTjyD|tbP}M*_j3w% zigU_wI_d1~oa9{K+`+lK^FZg(&eNUuJHP4tp7T}bJI;5V8(gei++8#-u`YQoom~dI zOm|uCvdLwy%W;>ht|_jCt`)9>U6;6?cD?9&)Af7TKiz`e3f(H)dbriQjd7dd_LAFb zw{30*-R`*Eb!%|9c6WF0<-Xp1m;3APm)!5TH+pz^#Co*wDDddvG1TJ)kBuJ3JdKP;w^W5mU-}9uGzgLJ?yjP}ITdyu&BfO@2t@b+P^^w8z@n{Zu)$WSYKhjy?lrGzU6z~ z_qy*_zQ6j}`+586{bKym{aX9A_p9+6;J4Oqhu>@dUjBjp{rpGzPw}7YzubS5|6Y}c zDo7QpYN2{b^?87OKtMoDKzcyyfc6130hB}~?`y7UzSR7pF==hI9$Kw7Qk$Y3 zrJbssr(L1l3}0kNov$uhm!WH~>#Lifo2^@-+pasJ`%w3#?tY*$&^0h15FsrCvja;5 zs{(rm4h@{3SL$8$z4bfvujxR27Mp&r@_ilW$0}fYM5Y{ zYglgBZFs|Q$?&bAG1xZPGgudl!)t=`g3E%dgNFq_AL1FJ3o(YIhUA4T3-t;O42=#= z3q|nR&~Bk0hdG1=goTBrg%yWYhSi3R37Zl2QrPOSZD9w)-VM7F_I22A;UwHHTn$%} zwDA1!^6)9)bHfjZp9{YnK_WaN3=v5YIT7t6Y9aZtIjq^O*z%BVh3W!#NQQt=W9?eBNM9+?161_fp zSM=-AXQD4fe;)lq3>V`Wvp8mR%(0lWF*jm_(AbUv@I45yg;>N^R6Hg_6o8+4m zk<>D&ZBmbIHfYBHf40m ztd!L$`%=!NTuS*o<@Z!Ys&8s^YOB&m5IGHFI9(ipPD-d zb7`)9t~S@0o0B^x_l4Y*xjS`et3R*eo20H{;2%<`785xw&q$Fw(i_|TI=^(f7be^0#aaC;9n3~kX6vPV06Ls zf+Yo;3-%S9E%>P5c42H`c42v8ZQ+Q*xrJ*Aw-+8Qe82EUkylYjQG8Ku(Ws*5i}n`1 zUGzcGm7?E@#bVE5U2%MIW^r+GWpTgaRmEG2FBab^{=I}NaV-fbsV?bPGO}c5$@!9- zC3j1?Qs2_(($=NjOUIPHP`a}8K!sh9Hni#7WuI<#eOWST~d!p_2w!gJ&+3vY^2g@AG+LiSv8&)>4Y<}6wvYlmz%k9fm<%#7j z%iEOqDIZ=wy?jCW`tn`nZ@15CU(uC|_N-h`xxR8=<;ltqJK1+qb&Beg->FNd5uN6C+T7{2 zPG>va?(}Dsb5&?nc2#**ZPnna$yF~^ZK&E`^=8#aRky2t>)fJqY3H7u4|hJ-`IF8+ zbm6+Vb_ws2*`-sLp+(sLAG(;jI(7~2n%VVS*H5ZLs=HOcS^ZJ< z?dspUk#5f2wA~WAb?a8w?YVC6bi2~+n{NNEv8@TLiLS}4X;;&uW>w9un%8UI?M}P9 zbXRpB*ZukKA9Vk``!C&1Jsf-Z_IR$xv>x+&tn2Y}Pv4&5J=1%(?%BR)ubxACj_-N0 z=f^$2>iK7_b*)crSZ!i$er;uK-`Xj)OKV@PJzIOb_P1VQFOObPy;6G>_3F}VM6YAL z&iA_6>uztZcTw*yy$AN5(0g9*wY~TBeyjJz-goNMbuo2Wb!Bzcb$jc6t-IeRq0iJl zYy0fz^L}5)zMg$`eWUu;_dVD5Zr_G}*8OVw&Fi))+^UH?J-=k-6^ z|KkBU1BMOQJmBI$GSF_IabW*}+XfyS_*T7ry?edBepvmi`o;CD>aPzf88mOuwZYoK z^@FDj{%{B#(soFfA$^7nA97}>a%k$%yrE@7Um3b*SirEbVTr?94l5g0J?!;i?+mva zo-w>=_`cz93_m^m;t0PHl_Tbjcz>kBNS~2GBO^zqjGRAm<;X1~_m6BGWjo4qlx|ew zsM1kYqk4}TI%>kG*`pSZdS%q_&t*L~`ngM^wWC8vSB+jj`tF#BG09^FjafA2m$80h zgU7~=%@~_McFx#EW7m$|F?RphH^*gL<~th|NcVI3$ZV>c%k5h6?2qxT;~ML$(+-EPR*RHa}LaT@S^ZymlxlD@q-ty zy!gf3khv@7el{;;-h_D<=G~b0-MnAtHO{Y?-(!CL{4w)S&i`=!r}MvA&~icHf{Fz_ z7St~ovtY)81q)U!`2MBnm+D?Ry3lE%*TU8d7cTr`QOcs+MVl8jEEX4MFJ7^D!{XhG zk1YOniTx7yCH_nFORARiUb1(|@ukY8?U(LadU@&Xr9UmB%epTcxNO9-$;%cmTea+! zWjmK0UUq8P`^&DqT=8;`m-oMX^yQN;f4JOxdGF<~uh6ZiUvYEA_bdKfNmkmetX|n~ z<;ay&R?c0yeC4KW83}h1Gj&+{f8Z{J4$yf-LYxMw>$o|Gk#~z&a$1| zcMje;ap&Bft9I_#d1U9go!54Lvy1L>-lf?Uvnz8~+g&xg2Jf1*YyPgcciq@6><-zT zy1Q=osNGw3UwJj>)n2c@zQ=u!W>3x zu0tme-8%G3^Z#CJ^V-{oD-NH2z2)mWkN6&$bL8aF(4&iwesoNKY{;<#$3u=MAK!BP z?i;P%nDNG|Z`^%z>YFFueD8$I37->LC#p`2I&tE}mv3df)#0rHZ!LN2t+)Ps`^?F} zlUXO%o$^0bbE?m&!KW6UdhgVOcZ7F(zjNdC-J}vhK?1D>ts(yJ~k;b2aH|>D9qk z7hK(Y^~}{ju9aM?y*BpROV@T>d;8k;YrkCAT+hBf;QEZ~%dYRbe){@%pM-wW{gY{* zZ2aWpCtrPP{i*TODW9(V^!?8ipBX>v|Jeth{d&XuM(mBE8+~q!xH0+0>Klh{T)L^a z8F#bfX5X9BZ_d5B^yb=|TW{{YdF1BFoA2MeeDmhbZ*Kl_^Zw^ivzzKl+nT+P%C5cZ1K81`H_mI)BgLY!qO)0w{gLhZ{7omn|I0hr}U6CX{74&!( zi9B({QA*jh8K^;OUq7T(0D&4B{|z{bYaf6wU;to{d>31@~M{sl# zv?im4IQ(u+ssJo)0=~?AT}1r(Bd7-h)J61uSSL%}EUA#x>{%|_^8W)ssN+QL9NGe4 z^+B}i(SHjy#E8CVBk)7MC&4q`=XMcxc0lms zfG`7Y++fU&e*)7<6!$p1OZxFR1LFSzgb>n)0h^;@D*9*$N&T-u1={p?^DvAg(!C@C zw4`72lwAXObIPv&9=^q#0|;#~&(6re<6LLa8ew1K=-{R0iC7(w7S(F;9i{cs3Nk(qgxnxX#oHcTw&!eHf~3XFc1WX zNRZ9|JI+yh2Lbb-hZhkA+oo}|1>lk1mn%ZqEX|P0wUo`$Y8oKjt4104 z-S{)LC3ZqE>C3=e$G+SrB)#ccuDA4z-_|Tof3)d;8wUO7WeO2^hm8>#V(2WiONJt( z)c{9x+JVkOdp{%o79h6~)w|j6CtY)kP@amY=!?XSE+yVl%C2Q_wfqME(eod{8svKt zJnMZNwZL~JHje`bKXA^e@y+8I|33hD5wXJrMeaEQzfkZe4}gE3^k5)dLf^QN*#8VeMRxd;Li=%E^GN$UvjWc*rYok|N4! zL2QJUc>W1#%^Qg=??YVpUc{SyoBN!2VBHG_eHkw}$@S*GM&9=%2*Q5VhA+gkOaSum zZ2^c^(^S_9;DOnm_!Z&{TNxWfInQAA1&rst04Io!0jrOMmwiZM00k)j5^2d_N6PRD z;D08z42Qs3qy%O>GI>uE9vQyOz}jiSm6WpY0#6JlTqT~3M*HWIAVeR{hu^vlBH_0X z0*MNE&;rKuv5kk(K5OnC+Hj7z@iW0|XW?D+ozy<`hqPZ$Mj6fa(0|z@I+GB1GxX=$ z5{IU}+$@xJiVWbp0pCc4wdjLI=o9Q=`3S6UEU#G}!1n5_{HAi7?*~~KOxE%(@#8W{ z3ig1lxlW`%{lE-+@!bi)dkylKfj1C$&VdAA57fU2j^H}U41>UXIN^E~U=-e4gLFF? zfOBdR>FLHsPTTkc1D+tpDFq!a1zp=j`tyrr0ABlZA%HH(Yr?&`D35*13VYNfypu!% z5f+c_m9*Fky}~VTyak`v{^*mIxV{hmO5he^j8DP%jwBBBJFHpxD5uz5Z&uGd(hucu z+z@uWoMOgf0@4HvF4^9)36~6KtnLiZRtvtQ6yIij!|*~cU=L}+ z{8AGg`Xh@2-ht;tekuAt7wtcQe5)Eaag%U=G^s>%<3w&JF|u#c8;Iq(h;(WKh7%3O zDeJSBNq<3uv@P(xiF6Xwq)Lc|+%W?2rx5E4^SwVjm-|a&fb}V>PhV-jYK!t?8NbZl zk@gq(-Xr{f^KY{2d8ADGmSsjOYtu9 zxGf8x3;Pp-U z9P!-kY5C|*(4U>8gE?i_0f1mYBp?K!VJU3r^8riEX=|iefVO~6=Cl>kHULY$4!C}5 zK9u>7zWe`GKC-c~M4+UD8FWubgK;l}jwD_*qwxm$8S-y0QbJCX6b6_dDdYj{`vBY? zqL%K(5+^Q}w57GAfSdpzAKv|j{svtJ%Af-F`Qd;LWDdJex+1n?25ABCq_Jc)z!O&5 zgJ>3srsd#y%F#Euh~Axxv^}18z#N4Zg_Q@~$4J-X@V*%=&**%-`woc*gitk7oR@+z zm0|%)8>ISOCoSo9%#S#val~k86J(O{bRXVxMCyd7VrMY6DRBfy?W5Hs36R24ST?5s zlB9CcZkh}_kPI9hA%q~f9PUfH95M;l_>*)iOqRyD5bIAAO8ErU$6D^V%ZkS5{YHNbmVKiTtlA>Uj<2sAkuedyl7?$isl$j{x0|p~D-mOLLXYS?Dg9e4F>cb2fL6kPcYWBe1?jfR`8q zm;-17co8rTFcB~WFo~ND`qPmN1h9N84|~p@c{8BD11B9(rVJHGhXN`v-&3U2^1CBR z5v+l`xuAjlFlKH6H&LLi4F15LW&_5R4&#Nv2dI}9a01}RkwjrT>Yswxfvle}PFXqN zpGI@u1fd^Hod9s(GgSdH0XxiTiaA}5bd~wK9_d8@duD+gTn{osOQdiomw=V^IKWW0 zp#k^VJ39gI0Codv%;{jHu;G=Svv*wyd2ku&S-kft(*LxsmO8SOee*x5pO^W&IE2U4 z8L$n2FkKR!Se+Tp7+x&jVCA#6vwAN93tbT7Bn5QCpF20EVdQyu{v9T)!ot-OKN#$X`AJ~ltNB17_?##p;&`YH;V8=O?z;; zu|(m+?lk{X4gv=>8gvkOI95kO3gr~XD=@l&Kf(Nhnm{gy0?j{3`=U%Wd4rJqocs-; zJ#|nM3Jjwx8;RbirQ8s$NvRwDW#4xrg3Sk@LJIuAG2*ymPoi)N(|9@o9xe;$TDqGa zr0>&<^gH?!=gS3f5uA}rglEbEoDy^#=Tv`+D1&yqBk#id@_~FXAIZn?N&G^73x9-v zL+}xFLJE%et`>U0-g>z3w$LcLivD7V7^SjSIjTHW{wj@1kE5HSREeroRclq5s!G*I zRj(SOnx>iyE88WiRjPlfZPiX{ceRgNrPiv0)X{1q>}qq=?bL(R kRuT^hQZ&Ghj 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 A00000 diff --git a/Jetsnack/app/src/main/res/font/montserrat_medium.ttf b/Jetsnack/app/src/main/res/font/montserrat_medium.ttf deleted file mode 100755 index 6e079f6984098cc53f68a24390936a73a6dcd944..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243180 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%0A#Vs)3q-KcATfdA75QVQ2KP!cRF_fZ&%s|(a6 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
0`*Dw7>D1{RG}=A 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 z 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
rKeyu10 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 Lg_BF?GlRba@aD!@ diff --git a/Jetsnack/app/src/main/res/font/montserrat_semibold.ttf b/Jetsnack/app/src/main/res/font/montserrat_semibold.ttf deleted file mode 100755 index f8a43f2b203054676b64e38e2b78af5468c39b6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243816 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

pQ9pV_Wo(eYaP&1a~#B8z|n;+Zi{ z>pQ#FukVun=z{2LWu+Snf-48|gP|n{dVBXr!eyP&E7r6vTfKT&+Ztsb5&cMV{t{~? ziHR5rpLi$y^c3(@cDNp}`nhO_v%RGHf7JmeNuc282yX1>=dqt*oZ2rL`&lfKJ?X-o zf^eV-A#ar~&F{l>JSWYQTqCRaRD3S5{k9nUN7L_4!Im(x$=HaR=$tt6f8!sCn|v;gW*4 zK4jm%&#!6u_uf0^w1h5i9v@N=zAn(SNOCl>*MwZ~kT4)YUZi0P~p{=A0r;9S2WM)w= z6LzPW%j_{9cY@bw0|LoR0bRgXBw)(#^0hP5+*x1cNxL|I)D5irQ7f2EfC zb7o7=Y4L6Cm(T-f`AgOV2F90746h#;WNQJ#u(bxDv2A@rV{_ly5=`q)oG(0q|3aP+ zElJjag4!=2_sn;rzyw0zg0gw1(H9&T8n<~YdCiV}3QF9w95V5N}qR?dKWo7fs}A?@ns3dn?3wru5&@u9D*>YTOoM zKA)Rt#%onH?mKE6@qPRh69oS?c6U0}578|F$Px7l6!w|94PlTzNx|Rg+O&)y-N5k} zcP9C9uV6!YD8DLLI<{rqkBf%0Dl260;va{$)2^d$(aJPaV>YQV`Am+1|5LU0Z}8Jk z(ma(ujE_?(WxM$blsb*fnlrRs$!TvW&iiaEn$V?uxM#NQq|x$Z8U zxyU{3Ravui&Lk_*O#JWg zM9lN_8qU*fC5m87Xe^3wsPNth4QaeXI`oY5kipR;-dG5XL0-u@UJ0EthZLY0i(A;^ zQW$R|h8rgZqJPJ!rnLft)@tw{rPyQ#+vlg6wa5^$jUCzrFgVT@~1H z1fQyJXnTY(s&9I%UrwsNf${o(slFLHFt%v^;$Hj_=^4AW@Y3ei;aMd+7t8$p`wc1I z7k!tc{Xl>8UFmNNg{$UN)L_3rF*SQJL9|Am2Qg8DuQp*-8OWBK)i<;#TlG!5T`%c6 z%U)G|V`;1p)k{=BWa*`Ai#9HZ(@XyL(lE`EwGu2INCBGV>xN#a0sLo_ZSjKd$V%Y5 z>+#s94Qx6?v?ik$oFS!M6h1sFqxV-$IBO#dUuh$v4Y-N=DKM*Vl2Ls_SH&q#LPWAv z-+(H(qij{*kYO63>Knk#>KoWAaVV<#ra;U0Wt0X>IZ|R&-R|GEKqRs(2DZX zS&((C;Vnx3U}!rFK2c*RdLk&DQ>;P1pJXnt87D%1l_-k}oV_gL2N1ik)5>kqgm(ho zi$SZi1ZNxYJvjoJ%tWlw_Lf!;XMojPT1ZRrTUxWy@^Cl8d`l}9X;d$G@Ov3XyyxBV z<;%w}8K6H0+AqDdy&Zv1Bg)uuXvK=Lj$IV(q<^s@@~1~gqRVLpkKm=d1n@I5G5nMb z{zZxka6ds*_5+{e3uRh-RG`r|B4wk13GTG_G?JjFDt&TKm^;N6;>K){=fV5-tPHP` z;tO$@br^ece8EnS3%2!*P<+9l(+N9VUupGRNpP&Y9zV%Wafv5w{``o(+QOV;!~B$* z?WdGHrU(pig*g^eD8Zhbbj$%3i!rzJ1h=^BDa6Anv_0yKr52(jP;}?wIRJVI3x9*A z=D!&}MeHRkVnUaTJvoZ%!^tHjjzh;zh4Z(NSVcue6-DJWb@UJxCrs&;P%B88&GlEd zm#~bBeu$6D+G;TBCF9Gx#|H|+Nk!8Gp{(AbitrJGb;VOV7lCP2nA{~~X+d#eer5ff zh%b7#$;eKQ%{MrCkHN`A&8I*e1)q>6+~j-(KWT^SL`?-hZ-^nozA!BFWcfer_8t`*=I9a_6r|6Jzity8E^(u~@j9KNxWG={OZYoBG4VB-% zB*cIqddVb&B*TkWs!RjVU72xsS8r%`Whov8&8>7zx`}S9{lk-HAwSh_DY+{4EZ}%g zW;$ItAret|N_}}?bdU%3xOvzQIa0F?&UD*G4T-s~eJGDP&r=T~<1D0A&Dw=0cKVa>Q ziAm?3cSbtTc}yahn5INPUY;ZZtVQf0NS?0+aXy>0ht&z4nJ+xJZeS!mbp!waNwPzX zlNvOGdPynL>orY?R6M|9PjyzASoCcL6A2ad=6^czaS^YwG8C$8s%)&S2~~v3{h=_W zIq9)Xaoy@=cLa#gfJrUy*P{`j?~cw#Tg~j2+s*J5-?E zsJJ`@2{NP&DXEMMGyVd#Vq}=(HccfERl=_atL!9QR6_YcC?iv1@y0rE%uOpu&*x3^ z;ogyv81&in5|U?*SmOq)3;CRm8z^nO(cngeLS_rStL@ML(t+V{N3=i2f9(B97y0`kpp%0RSIjsMx0DsH|KW)Ha z*YmsslJQM{kzeLMX2ZW=;^VFft<%7_yu`)m|KqIV)VS{}eD)4)_{uwkACNWio_G`e zlv0H^9#;^4Jb?kuw7)3^3SbHRrkKjHmujIzC>`~pm~qpM-xE{Mi3PuYsB~^< zw07UT#YS9Oz3X$cMrN1HD$Gqy z$y+q9=Tg!?YL28^)K@cJmlaG)hSTj$h6hn=1vmuj9Q-6Lfcz)RcJ*%9kL1IU@;TUD z+SxT&5`_UY(Pzd0^kC1SwB7RI=g|KV-{}Z5M_;7d!@Yx z-chxhQzjf7V8<6GoOg&kJB9zM1#h(D!*VyNLU0qGEPBAff!vNgzxU)Z@<5h8k*o6%?ZGw%KcSYBDj6xoI&vilXt$Pb2*}ia7YkTj~ z<|TcR`AfRS+OF;C-ZOtrsknM`$CfLTy*sAQYp8G8A(k}Nwk&UtriwRL?_bb3Jil&5 z9ymht+}$kGd(Ch~yW00;sRtgz15VoZ8C3`-rF@WxJq}N5PYlSwSw;Z5H?Y7YEmE zTDNZO`0;S4BEKL#eZx8-w%u^UwrE+opUzww#(W5424okmRd~Iv;>-ln8HjT~H?f@z zCg7-fO%M)on0JA3?<^IZPxCe5QbOtxJ~-SC7Y5u#>(j};L_dg$oe)3M4&W@|Eu;hL zETux)*;<09jUEy6dcro6o8X{WKm6!HL|rNJ1KG~^B&aksgKtD&TdaB)(qHPH1fU!O zHC!q#K%EW{p8iJhvwF&i?n#ioG7G)wqQoFVRSmlijxA|jwt~X+ZK1Mj_v4!HAG?s2 z5HqlDH5-y&D~V3f1*)by9)4yD_~|L&r>1~kaKOn+R^y(q;EhZj6FyK);4eZI^S}@$v;%A!*`qPECpPC##l znmCYZjDr;?BUn?96;FO24DoLmN0Z+NEAs|3)car=;)}Q4e)}(fbjQ)7$6o%`^UtFY z!uz$aMK46pU}+O)5ObwH$)GzX=`)j10gKc!Y*D&rLeU)Pn@!jl>yrbSr9moWgd0sl z$4JjNH`7~cvg3SkTj%Wl+`9DI+>*-l7f zE}d({Mfn^?=$uZ1pOSVs$qj{n!T~4CLcw3P;ElX46FwpTn6Uqo7Cz{}=U4S}-VUdn zoYDU|2b|8c!aqxJ>{J+k{1x<^;x#dB7u7Wm0~aX^{2BXnMeH>(@z-d*D2@IJA8GjA zwrwlK)@>`Ip9WW|zKLenqb14g7M5-C@Y4=B(MI9FYQmwpXpSa)LQ<69rL}<L^;Md1o z;^-7uLlNJ!Y14A2<-oS0|Ka(ueCw{h8*df(&9FRuQ7HJco1WMpzIGEHAA9}Kq0i0< z%&H8Qjg3HP;_b2J7vK7E@dlNN{2dGwBeqFCWjeg%M+~1bR*DM03x9!LCBGSd7ir?$ zSRkOx2n1lY8r_3d?QM~QGSbW?F|TqqM+(U43|LNQ0vxMWxiF)EYn8bi6&Kkjm!rnH zofIK}-`Re!zhSU=UCi%%i^@qE^9UBm<_mDGC4D` zL8r`wb9S`u$(f1R?5Oh5zHXA8iVsd$aN0%k1{IulF#(-UTKF_)ihC>k^L9A<_X_^L z15UD2!OvOne0gLkK|myXNI*~@bVUrZFljW^ ziwZgAb(Qr@OHP3@RJH1}$#$1D0mH*|{V zXy)Mj_3K2{^uFfkXZh;L*8IZ0!Mgg!xpDVKmfSF~Vot4CGt`kZzivVN?U8Ttsr$H+ zHsEAqS6yEsI8}mcLHTR>HU9l+*PHmMshYW!`PE0Q*3r5ne6K($)xCe!T~4CMZsUS;EkXoNh}5DRVLV{lNLU$g(Q~3KW~S# z#8UA09dMFZ3VzNGXCH^)lyx)q0Y4{a-8vIsRyi@5?};b!QL9_4wMUj#&k2_lyItcI+vCsjmcAO(!`0~bd*k>~++J(pxg|T!5@IVu?ond2{ubUx)h2T;; zJsDwDO`8?duiSsn*tQLw6vi^@rDemKZCou))h6$WKGYyh(@+IR#7^NDJkk3o#zD=c zkhs=JMmj#m63B4zGgrnndJr-Awc)XzT-c?vz3W%W#TS3FGG*Iz@Zv)B%avLZb?As3 zC8*Q_#?4fzWnjE5`yuch^_qxE(cBmVQ7j>%EO%(mT&ZQWOjxO9Z;2SZ*|J#%xh{pO zbZ~MBc)3ygxU!Yg!lA+f z1O{DhT+O6SoE6EyU)=;o`MHOo9LVL$9uM8dRM#oXdQ$M29uyYv`v$C0u|Kij!qJ!l zaVRpYq9PE`w2H=xhPl;&^1y6Ns+cmAso)P&M<_jNY6|n-rCnb@eOl>Beb0jS(yKaG z9CG9(0qHy36TLKZUF-0WXqs_JOZ0ZbC})M@%uAYDb6scByt-C>S2zcB7RyHG~=fyV$sti?Br37 zOMb_Qp8jr9^c1ll%!Q(--(l`pAQ2@4Pa{^ub2Q;n<|;V&l;&>2g@VJ2M?V}rML!%p zeJ(C~+7BPp))M+k^%wyke!|wHo8UN-n3;YIK5LPNI0Y^;fJ*8ujDUIzW0QcV1_A-O zNGqZTwt}{|FbvCyidVTDA@(2ou^X;B*pxrG=&I|-8t2b%8Ect8zY(#Z-|oL@RYmzt z`$bvwFTGvMdqjTpFFnhC0|IP9V}6G!4?a4-n_;5GeyM8U z3bn#uMrmn4y`C}oqD3s2jIeC=zm3AF-R*(-`dMiem~ZpMohoG7I5&A9M#c$&ZCScQU5=s#gf$M<8#VHwUEU*Vha zSA|cSUBS(Gse+THQgDuHVh3Q3i+^C;4}C8qfcGK1|6ktA5dVnQU$^Sv%@p6cX6>Wf z5#hOa!+~up?%aTH=NjV@-F|J*4H>os@Y9rev*2VYDg0MWIL;T%--J(arjBE$E_|a4 z(H(qi)Q_h>@TCOU*q&iXRetBqJ3{M$xotdZhgsxmITW73nM&|zow}t|LItOK)VK)o z{`ed6)ybD3`Y%3>kH`F_B#3#Z!t{TX^pW@lMhJ=%;(N4SZ@I4W5cK;9ECKaTQa;R+eaAR$4<8iu_C2qKE z&ow=HnIqHJuM+>!)ZE7a0-*nMo{jva>%$<~WM`0xMu zAH+>BfG#)cGqB$$&>^f2c^Tq`oNo?uzG@Q2nJF-is6I4$`5t|cw}GFJqvC#yb5|4+ss&cSI=RMQpbB;ezdX3IEET4(ir zehQ2%3*-I780`P4({gSy2C`!h#^#3@)mKRV7^i61>iZqDW!&1oY+<}R1%}VUIBR04 zH7Z%5uf^yyk(X(UESdC5K5p}=XY#9U59`-xst!COEhF8BrpWB`|E49WXPbhMBh~}? zjX|%zHrnl;)B* zscMO8`MnM(yl^n=#*AF%dmUUH!Jl!30ZHDUNIpI=aD3=fD+d-1H4oO-4mMxfIxz5w zl)huDSKrdNxxa1h+NS2UbK6&JhPLBbARZQw?;FzjHmyu~d}Vy5CSjbGlQHsnCEN!z zrId$QBhCh=_M3epHo?v!Uk+=*$B-Z_nI zoQ*sGZ+$=#(Tcba*zyxnSsH`5sbPcTl?elmRM}6>F}0xn3f=Iav0WE#G139Z+5LVPN0u34`_> zKVNjx?L{RG6>m>&Q8jf7@M%Si2GG}wY&3wbHD-6#IXg%+lFipaL->(@z)SOZ5<{m~ zh2b;6TY`N~-$+)K)N_rbFo;~M^=4;lT6RHpeqJt`W(DxAPS#*b(y4EC()+^Z3xNEo z&d=Pj0{UUiv0m}Z)GeKZiQV?rNFc&#^!X)6@%YA;t5&xko?RIjZCu>3Y+37F(Lal)KQ6k~Ev;Ww zTUlbghcVOV2OFs+EJ_5~N zw{G72xgz+NKpCm_W4)VJkZNxloSQ>ujORq!S1)BcoRN1V(Cn*i7^goJyf7?it2x|@NY)FQxT3NQJ>Bv z*?FlNzSoGqBT(rYr7DnWj=XiN4kPa4WtQdO9PL1_+-a7&9ovCL;#rD1(QwhKoN?qcE=2>v>*F(ZL3u7yeGR z7jC`VMnyEPY2v*6G`N6jYqDUCE{mX)*Oin!^_TiEzWJrTTKZC7Zces8DJvUqlI%fLvk*{tL|4rZN`^Vavu5}$fHy%Iq@{Kp@GjG5B_KW)$ZEEQ46N{rS zAGuvFjs6(xk(hNwZ2oww!7+dMJ?{Xc)rLX$`fV!&1~NUOG=_oq8w?Cf*MX`cUr)lW zGL^iI@ffJF)5H?i6u^L{s3Z*16!^)SLiI%v2t!k>0N(F)(vFJqubO?@wIlBpV~}

YDqo1b<&Qi@AH!|6x~QWGKP1)69aTzjC~J)tLUpL$Z4UR!2GHT zWD|c)1^bNCfO2#xYe^oOA6Cf-dBAtZ2QE<5%QaB6xxd*vyP;!4`|=y_KJ?=oZ}gND zIJc9e66ys-kae<}*F5LEfYF9GCH~GQ3>Y|J6<7*Uh5VDbc!|wiQH!%BDhB^PaSoXx z@=amYVm-)JP&GQG%qU&K%dtCC#>YCK9OFOMv43+xUu9(Ar(*fe(UdtAD;8`KncJiH zUa<@Fpiyq(-D$^+yxu11wey=Hk8Z#`CcV%=aWwWZ`Gp3GpsBYa1iqCT6s0Q<9~LQh zzALnk{$!oIsz`wCYG@G72N1$f3n?6G}t& z2IoHw1!gc5xHR;Md<^!;ABLws%C*RH$bx4*eUh)vNyitOk=fu5fb z+b{k=&9G6@C;{B8#-3qCl!J-svt%t4DWO6j49-Qb)m%f27zNOb@=Rt7Dh=F9xn2y*-ET05Q;GbLn z`dUQTy6KX6bJONzhr=RzN|7U)dhPzu$h4UiRifV{3-ic1S1a!T&kMZsZIgElO{`Yx zCQWRulw^=v=~?GW4V(R8*eo-V?6bX!FxU1f0&G5h6`?4N?qu?-2uWG7c#aW~SR`Qt z!%8m2LtB@3``S1s(nwR3CN;Llpjmu$d? zdDk}?yMCN^Cgz>SW1Nv6hH)w(#>=2cK*uQ1jWvWpx=~-AIL?fDHOA(}(fQ*%KeKNu z&TOn;a^F=S&^N7_yl3ZZ8ua|cab{x-@}8Y7>adQ4app;MyY<7%LR8|)(3eV2$E2yb{eC^!omG$#3ZNMoKKiR#QJY4ppJnleAaX~?4Lw)D8 z=w2MDhP8DyYnySTOxiulXKLS+XR1*WWdb@d;;3$KnV9O2vR$Y8j-$G@g~`ZYH!~+F)YiJIgPC}g>f!12HV5Hz?mnmRgn{edt9OeRhk#CKi~r#Q@{Q&*VP*?uf#PFc+)QRl??>MJ(MzSXpVpU zfmnu$X%wG`Rkrs&7p*+psf6tn>!PoKJuHD6UY@OY1Nt-_@gPJ(%*ZmOvyISlG76~hEny?L%#Wkb z%AnVQ@cuPIE#Zgx~xy=B?5CHwZt*4>*o?|B$@){p7Y{9jokE`(K>MOF>6 zOe(xipO=T%Mi1FqgHFel$>~|sGc$ZZPxbk1xh5Vp%Qo)!jvc%Dx*Ko2jt1VlS6tY$ zY15t&xo~OpCr$Xa%rE)89F{NQyyT%~54J~MCL(b#TXmO&^>`Qv_=mrhNiSv6St*@e zUP5Pus|w1T2ZSHJX%6v^*fEhW&f6Thdh`BSXurrjM{OxXilOXy`QgM*eNnboY` zH(OegnQ}k2mMZrHGr166X%u^3Mm&e_%!nNF8=Nl_Anb@~^2-D&n^>lWLvs*-H5_22 zM8h#gP$5VNQsZrcm&U_2p`A?&ninr!wyJf(p6-^*!AZo>Bm;S-O!>PF$}eTg8#xLK<6%$%7|<=% z5*V;2=in#DS`4`Zd&kf%-J)Eh7Yn3Z$o5VA7^kTRM}A3R+-i+6*EYtNoRR-8DO;)# zm4-xNH7m+;E=@SrsmWuZ7SY~PI9ih5q$u&~6loRB-4Cvj@Wx z1YcYN58z7KriHY-d!EdJ3FZ$7-5m;MX27jVPl88Fmn!M4-UPpJ;TN#ns9B*cknbMB zbB16hN>bBf(rmy55+Jx-HTG~nzM+f{frWwyALSXcHDCk?sixJ--vLnH8-+;oXJ>h3 z!Rq~6mN#UVH&x=oM{%+Lp7QGcfx{0TZP^#zgakmxs?GkyDx}yFB}%X-mxRf zu?n+wK>s9KBFF1~!-x{RVMHVtgE^TnC@NDC$7L??49$T1fP|31pflhCN7&)!>lLa; z@)}f+C(Ir#$>9P=*ji%ejUEy0u)!f005|r32s7|eY+S{}K?A!Gz@tE@pHo60R$Qrq zdjEC%;f|$C+7I1&Fy+>3MQ8Ne-Ti$%Vp;V0>yBX+#9KqW{?GCHUq|fUdCKB3&U`4w z=?}#?^`RISObm-^Y92&aHPaIbFd8M%i80t_Kgv55zk-uyOG_dv4Dysz-{%uz{EPgJ zg%L&l@nkyvi%Ch1zETJbaFn8`BY$gT&@X`ovHG-HkcWqPJ0u;=FHzrWC5ii{7RHLK zzP%o`vZ-5;y$TxGTAXECOx429J8zj(i`z5Og^I54n;1^YvzMYU&Lzgs$>!yG!Ak*$ zLX%YDr$dt{o0rc^zTyL|E>e><9UjLF!{dNDjlDO=FJn|o3d9ApKrkm%ZpNd$9I%0J zL8UnCc%V=xR@E(sx;s|3C|{$zxwU=q&a3%|!Ge5#@cR6cc}0G5FtW+NnD&v~u`_3# zH&l4hYQP=u&}wL}<7j5=127c*?R6E3Tgh)w6se;=K#^+vbWlX;R#1e@#sfGR@oytQ zj&dv;f2y|;U@!|rKk2&tfeT~rAN%@q6o$mY)`>5pA7s|_XVFiBw-M}dP4jI83h$ve z)gk)SpZ(6Un~&Z4o$+IL9k>-f1_GSNqgSH$t@t41ht6NGV=uQHb1bn6c13u*6NOp{yo7J)Hz^ ziTzE4=Z@cd&wu^$?%Qs=^H=}<^2^8z%ps2v5wVd zAhAFZ{8D`Am^PwbY`yV~TfXpxBQL!0qBtD=tC$Y$qk3UFeczDVE1h(jgmDTlZa64u zU=W>DA16!ic|R43-TF z<6L44j-)7zvrY``V;M#%#QdgsF@Y+}j3Tr6kbLaL1X&nO79G*=ij&djw{IU1M|Z4> zj)7OXZ{CAfj6LXgQg{-^X_**)>U5tkGYJm@(08iwv=13g^q^FRaUm&7eEGmq%BC;9 zcExR7OO`B`?TfBk6MgB9JH-4o*DZ=ZzOJ*oyK^1MF&drM{wrhcH}l$KQR#S$Gk9~D z_%9yg2v&wZ;3L!$2FtOV6IX=8TEUkF%u(59zL`fi`;J9l)aaA@uEH1ztIdrSp&~qP zHVtTzup-=Ofr{|B7Ihyo232e+s?i(QiD(kTIF%6NMUK=fs?`t%QB7YwMWlWzs+oN| zskS*m-&Irh&3r(Sdgp8!^zRxnR=7lvBn|kR(H&(3vMD zV-V-C=SQQGjRnq0z{e-emZpR=`X)bCWSpVRmh7eyA|J z!!|#%7gWo4%$9L$M@6`S@$M8DWKpO-==ER!HW+x_>8)`K^1yJh6~1HbL* z`58D9DvF_K_9ci%^JN7 zS4>e62D=J*WP10{pp%<(A^2u?umq3T20VCO!MI7;(9~?!di$-2Og)~b;}7@3(3Az` zhug;Qtg9>u&A(=K%dWCe*HGKCsyUB-<+84x#vQ@BStTQVRqa0C!m^tFYJcYDaAl~l zWNSyLydanOnCHst{vTuA@%~9X#u@ox7^f3pF+L`HMvc)f&lrq4`nP$=G-NOyIp( zGoRd|Dsc-wx%sjbG-#&p(V?PM&cdlp!>O&t9q}4O%skDwo9LlyB#5l*I}m1a<1YCq z!c6#QD{d=tWoKCQmhn+84J3K<5n?yILG|Oqj&!#9cDXFgpclz&3&J08z{_rChw^8s z(-zEK+~At>uy0!3os*yJjqNDObw%s5PFiDeb&z0GeVm26Gw_9}@;JxuOHFY@Zgsn; z5(bfZ2rzsL6?*}}g^!Qt(X%I|VK*KmAS=81A^AkfO#Smc);ni_>FIKJ$YdE)8p z)r~c43cJe2dY+EH6Hd0NW z`$e^_<1GdtQAsbt!6odXr)#K>@nz6U4D`MPz3j<~0x?D}o|^D?{`IeiZ#!6jX;E22 zpZKput>vTtCWiOikd?75K;uz1lxkcHwH=XkdhgkX@d{xoyYTKaeaAf;0n#E8<@^!^ znv9UMhnvs%(!2UkdV{OLjzN(~Mv(L^&#=U+*2$;@4&Y`bB)>p0T_bUd@f3g(`@|Cz zqi;qr@VeHGYwuWTD+Zpswz+UYwg^UlADGj73o5z$s2q5(xp_@ZMmp^{uY#(5U0_t$ zIUi6DY+!sqJ+Oh1s2=zqsvcNja6K?QVv=aWtVu8`gN;5upfcFNNL(5GD0W1xg(`#J zO;{P+h?P=BV5(LY3E{JJc)bVJ!R*~|EVzX0x2*duc)dX2HT053B7aGi$lV_O`u6Rj zTl^+^d~{S?xt%KE%s!>-|MB)6fN@mkx^reEtt3mb+N!tR)oRtN)!VAe>XOxqEm^W< z$+lc@#g;A04NS+BPyz(UK&YmKB*e+3B!pB*AcT8!a|uaIxFm#xga8Q|z3)F~X011yOYT<(OUc<>X(R3r#rft$2cH72q2CVO%tt3;MepR8EWr zv`X8te_W&mXq7hJBG(qcj^*>(#w8SIIZ2`*c`4OcYWZSE1{;2pVB=a9S03 z&sIC2^e|~Vv?mK(pXA=NXEpCxV0$O;SzuB)AYwYe^x%Jm0Z~#g9r9g62!}g#0*<-h z+)zeDa-dBu7Y?)oaG)(2Wg9lC_AQ$+3wY3;WiuRJ;^@QxqC6JCT+jSj#mT4BEslIT zF%G<6c^v0HPU?C(8YUfBj2q8Il4B7qyF2UgG5tUIvP!u@T z(g%pbTX9Iqfy5=tXAoFL@&mb6QVxM^O2?v9O&u!BFQ`0xSwYRAgVEi&m*vKel*lzZ zx5BM+u%fiwuAXIshv<)7Qe-l>CnnLp!QRWqZFaNJ3Zmv#ram8egj2FcN6jso4nhx* zN2G_lJ~~NDMheOYq+uqccp`HSq$rUu8#IoWf+QM8`WdHEkVaJ8dUx;P%96_FL;Fj1 z$u);}s1LE$s`{pC^=Y}Jq=rTaa2?@$Vx>I+7Q15co-Atd-qIGZhtq7#Zs>#C!%uS9 z6Mcj4r0eMWw@aS{ckc8g&N7~){f2)iU5}PV?0%6Xw~fc^V{H$Dbj1Y@2) zq?7jX#C)JbWITkKynFzl)*u-SAxofIBg6*cD6#);?m5)kl(PG_+jg^iQhd}T_6whs zb+xsGAM_s0Za5`mdG&o$v%J13EU%&k=Vhn`SzfatX>%?^L=?||44nv#iub`Sui3Qv z^B)67ov$;hAfdlg@fPjT+5rTW}=ifd+eurBo+0VQiBEW}5|&p5L4@-x1iy>`H<#q#!A=N8M`Yn@vxZ?AQ3 zvAn%@K!GglCPuovy_Qy&x8RcMQGU7>&vAKs?Ep!tTHnjtYo8%|t=2+CjWieQ2Vco& z^mUTRUW*ov6?Ku{o$R%aJ{?aG*Me~i$aS>*yS!ZAm$uhFqtD3E_h;E_pW)m9*4OEY zOWSLm_iTB4?Eq|H`l>B&uN~mri`doW?X?4Buceje^8F?4wH&6S-aLVnMi7R7wO}I z-h%NKz=TcnXJyJ65+jw_i&rvAA}QRClm?Q>+?1n`oGB^TCQhe8&3X0tLhr7zdL7R3LE z7QfY7I3$v$`4agUhhouUUfBg*5~pVdu~Uo3L0QF?8wcSv(Ff%hTc!m+f1(9t8e66X zKNF$_FctebWs=KYD z-13{0rtom&DKmw~MMt88P;PZ^;869i$+pB`BaJl*V?BkjQdQO@ zdjhZylvD~EC1I3iwMkx5lGVqU5~wShG^L%RXb{G6cV|XM=c?Q_85wJGQ*-lDQu1=ztw3h+08%LyV&4m<&&NVJFyE%5Ojl}+lpmk%Ai{-R#!AHp}3lt9uF{_L#1;)wbbE8KG|H(Pz`(eld5@<&<6DL8EO_f!mS zl+Um2&TlhXv$L{tRG&>7e*&=O<0`|r^w>-8;mlLpK@UZ-my~Vagz7maxfEjL4Ev=_ ziRsQ0rlL8ODTe^P$YTb6Hc;M$zgnM24P=l|4IJN*ofA7PmKGC*qyzhyKS=>WP-`K1 zi)ECJ?o-ds$mg$D=bpfR_|0!t!uV<#CHP@Os8pa~MZv`(h)Y-_Pj^LIFs>~NrW8)_ z!VWT_V=v10DSQEfa`KQ}&J+OE203UeVf*HOzn{fa<|3aeQ^(9!Hmd%~eM+6X`DReU zZ=L(ExhMYft2XxQgEYJKnB6vvL6$Pz9N%fS^c}PHM~uS4_5F_P-gF(a_J>{02hSTx zBOHV(p->D!nTEMQ0^ESa=rj{A@+8L`j3^(7yu7q7-bks(c%DFoijpAk75@e0C_xta z#eIkOJ$}o7{`k~G2M+Il_|%Vnc?%M5{yYEO%39T5q6`N8o>Ona`=i0@_o5a@vQ%y_ ziG%4g7UwP*I6_Z^;SM)^kT^vq)gJeswK_aw!4FvDO~`~nnqLlg#0F?ljU4E5HR#h& zgh*OskOS&Eu57QprX)Fidw%OgQ`MQ}LwyvT@&=;+M8l2t9h7y^P`!`gXCG4@CUN~=gvctRkFL$ESepa%v73INIp zAiZ<|vy9HLefwwBEB4Fh)s^fobJgt6s=p4UuPFxsr8_y4_CbbSxK7Y3uKUX`;y$ng zV6O=6eSxhS>~vKP`m(za2iV{e9NP-UEK$8KJ3BScZenKijL`2oB5MfycXgogq> z@W^yhteFFS$U`N;mLoLfIY6;YvEQf9BHGwgCT4{MgiUY@Jg zSL_wM2USNo&PW6A9SgXUrDK}5N0NVn`v&rS#?%$mep8ePSdjcp^_9eJ6{~XMk|N8KR;d4d)yqG}RDRgl7_!l~B0%<5H?TWs<@g)Rj71(p5n1>P>hC&p?;326>7kC@RqX$d{9Z?0P`zTce z!UtG$D4fHvm!0XCL49_;b(UXz;r0)Tz6@>gv4iKK1XIIJ`Ph-sz7sbb>24cn zCpcoB)+77h{Aojrl|c~?Y&*YEN#nBscSeieAy5YiLJC_8I2Gr@*(Sv)EYOGvze?J^ zxkqQ%$dM`a|3X8H*EzDb_j$?%XHiy-|;*0OjpZr)QWv z1F4K4U+YwE;h1}Vvs)wjHM>Es2CNJyh4{?whtQzF zgC-bUa|sjTh4Od*zVB}|gWI+76CJ<~Jc!=Bfr7v#LWY;8A*kD+2ue z(3Ya~xTTr%htSgVXgLesBU?`1bV7Y~=J~9SyY9cAy}V=Ft^@M<_tcwfs}8Fl<7MDr zB*lz35?>1YjU!4AZ`9Z_@DC2=kjLo-`%(a!7NRU%om#GcrY0Zfrak z2rM7t1)c_}5%HLwD%XS(W`4qpU@8OOAC+HZE8VV_UzE>3%WEw#P&v}>0+zyX|5jkwD3US5!=NEp z;f+8gej*omC}Q4Sm?*4OloV-(MJhq)^BoQd`P^!bY+~eyz@;qqg)@VLXEyfTklE3^ ztAE;0{T|B?IMB1Jxg+z2KKaF=FOQFZd8nkYZOv5wc-N*iZG|P`j7h)6{^)s_95~*e z^xKq&X>^7Y56qEBLy1D1YA@}j!JGY%@9|2+>Ai|RWN8x|$wA+7GXdX?60%+gG3Eij z0=ORzCb$Wc6>YA@iEF0{-NDHMdj(~%r`ReSE*?OI&EkH!F!;##9-C>ct?C7_8T)j! zi#>4pzNgRDH72O<0~V)VGIO{Byn)e0lDGtEN^lP*2=WXb>l@(4qO9{Iqy)wpjF;de zK!YOfNsc!Bz&CG1~8@+9?VzQ)UvZ7+5v~;3E-=~(V`_x|m?B41* z**JG^+4lPS?PcPVW|@T#FshJb&yE0N91cr-962OOnq2J?fu{iwTpxbfb=mQg zo6@a*VKKS&t(EoNN8XVq)p_>V&dDuXJtqx$x%nmW>RSX%-=)S!@vdX<;CJ0RSN7+Ta^v z2nh*gq4~bd+2yEZHD zuW9>0-n^T=@YY*|19fiIOx!AR+ZmLY`3G0MWG7rmd1N2daN!C}<{$D);0^~G&jkY* zR(JtmkX8eQcRkR@Id?Sg{1 ziQ51K>0wYTE3Wz3iKxD6bB1qNP(qgR;UfU*Ikvr{dBs7GoFX*=@C>z*1>xDUb7j<@UnSH2Qr2k3|J6@yNP$O^U>5(XeRO1&^MF~NSx(`3rah*Bi&+u!O?*%7@8rEAT4HstDSNQ-R%ve=RxQ>F)*PyFu>}XtgB7 z5l2d}uHhRIZ_0H!W=g(A8YWiO(X_9+e!3;z91;|qT9-7qy1ssfy|3QDt|{Byw&rNP zVUt@*b9QrY>ObW3Q@^ME^9Ns)jj@^Cw$nHagGvR*@6q#{xhBBuwu4`vlD@>Rug7(8 zo@g)RyFrFn9<1Vetl|Y+4<_6cNMPx#xDZkt&JYzrFax*peM7nc?MG8!aOR2&nsi{e z{({|N?Jk^|DeO*s{M2g~zQAlpjtG%+b{0bCqv|bqH}DE7SkSw{0pZ=E;sTO27)VcH z!0-%Ep71dsKQ#kmy5!AbAN6A@-hBD&Okdy3ZMQjzx?vsoIig9scr#Hw(uDaD?D&vJ z17Tzc5nXaYDa8CY&hSJQq0a>gKIbx&V?Z6g;wH?ynDcDD+UTievslk`s^iK-9N z$Vp!UAsA3}kVDsi9FM=Ulr#Sx>JI-%qAEw<6CN>vzCN7aD^Nh>PT?zqNz53vC9vYLg1Ri7Z}oI zOVvF&U+w^#ed*C3;x-rRi^MXMm41LLs5T0h zAt8c^xGgS8J`YKfy(6c62eQ~!NRz<7Q&Vaz7br1`OpM|Wd=$sTth7;NU=~MlJ(Sbc z-~dW5Lghkawpzl!pxQa4b5~rb1s&kz9-pC7s4)n70%6M|XAq$%0R9keD0vW|j~=Nh z@7{OhNNI6*wVik=<{9pJeAY0t^Ua@@H+mmF;t>r|nsJ%&X}%he+E;?B6P&ZNi z!@CjYP8>7$0S+PKi_S6U1b|z%-Z0bMHrPHhGup@gT@%>V^WZ5zw(93DhOforVl8AJ z{DrjzI;R%_5eZTTX?~IT%FW>I1x8YCfL()}=-i}n(pxU5d}Ynw*JiV2*wRBSR85ob zkYq5$$mHD%SSPTz;R9)lgeQ0oxMQMWNc-RGY6sTnO;HJ-Ar^!C>NyC;34 zdPbx6M5ZQsuh?|gz{uHAb?s=CH~W!i#YiEmj4A3$PfqG6D5|@1?Qm0mu`xN(k~Vx@ zQ_sCyKFl60Egj6Jc}4-h(0t5pPx5(AvDb8rDg6%fm!)2=1LOL3u0ayiuTKWkMSC*8 zifaWTc)d)pf8e&E!zF($fGfFTq3VLBnFMnR6f%tGP#B6J)o6h@5A_k@Oo?g}K8oifp2!bsQ0E0EUUk8{WTA5rZF9 zuCHE@yp?>YsC(60K)EMp$$-LARVeJ$H<<~lAG|9KCzbRDM&coY;gSxmIci5^#TAeM zjXxRzy5TFYA4a2OBsG@Clm+m7L%9TbPRgvqNB18X?k9CtYj+o&Mc8h>YCU+_%g8kCX5Q8>%J2Uj!UK+?^pc@*$ZAx2Lc4u3~c#WrO zGgNFWmS5~TGd1<)UL4feu;RW9eu(GpS+%yjeC;Zl1s^SG8r+`6=xMeu@!5(qO4r31 z^;Sywb#ANT?FEPGEl=S&VZ&;|>U`cz?p*t!$9{nGK_++MMS1Z|?*F;nKD(~%hP3p= z-Q`W)9cwBdQy*Y|`3Adw^P1AJ;$-W_xP&4Q^qzm)-17s>Y!sXE8h8%fY7~P#p&5(o zG&AfspRu^kWh$B(w*b&e{EWB%T(TDWb$xz(K_&wZ<9#{HW@yj#RD30`*T#XB#&w}z zr*Y5l>oKTRvT&WoEv|dJTqo<8xNg+0XJY=xc>jU|duyJe-~wgho`o$~HVhPMtD;=T zTa#ANBD9LMI!YqGlU6N{b19KhlYKQRn7Whq2CxKqQOoW)t57#FlHj~i0@`y(^7Oo3fNIrK5j1IrN`L>sDIeYfjS8jnf3(Fs1$uL2@K7e;+LD#v3TaKaY zgjWfJcZrPX5G7f}i6UObo#J8_M0aSV$&mvnhm6LlKqz{oCIf*?QhH5o`<3l=e>?cwn0YLb&`E!7p#r&A&4@A5H#=%vV%kpC+>G4$aB)X6UE#M>#V3KkK zyx5F->jG^2l(S;h&^Nrm*)c!{dAY=4UCeIWwgO!MSOe_eN?eb{^%c-XAnwHvr=2?> zz%a*w*u%ixUmRkUFYG^*sS!V(3(ha3j|eiE6eV`w+^_dF&CPtQu3(c-Xx=U)QLz^Y z9=-9(KjCd25^U0dKj9}2PYdi1;Svmqa_Iz8MQ8Lz)fMI8cm6obvS(Pf961L~5scr8 zlrsN)<*@t#IKC*!BEjNkF^7i*_<5j&0q+v;f?<=pSTU&?i6F^L2GIdxkB9;w!z^&7 z8(CyMjBd2AwgBaD-`Q=KPu;ln$aeCxUu1qI&2w+;>6 zI_#aByJFp~L&LLv>@B~e!*3lK9-dvPLY>4oZ6?~Zd~Fqh!eyGLUR#AclQfN>$?zMl z40Nahp==|{b}^mW#pLmH`2IV~>9PzJHTr=i24|J9!{7PdM_})B@SW^x;GF4n!1Kw& z1MYurZq`PmDkW5*2MTJYvPiD34&m!cMhFf>o}m#k&>;Ogd4+W+gkrd(PrP5e#T*+QfwT`!u0FnGgk9?DBZL^B2~a+P zGgz*x&(Ez8Fna24K6veHUrXoM>>{_HzR^3bMNX+rL+dt-v8(1t(%|dL_d%?qzx++C zBLmz>@(;0JM#*N6$ANQ0u?zwokUmFv78;VhF35t+)D)ZL3?LSp02}u46^%y++S&(S zV{00(9FSjJef5}n8M}=`#`qL~<(hiJRvq~Giu^oq70<6520j4>0B~`TC@~%=8#vJg zLnm|7D6l$D;S;$s!We|7e1n`U=I}8CZb!b1KwWtTUyU5u>z*5rWDn-e+d2V9jxtWI#9onwmc4+(dL-Kcr&Ww+r84{m!PaPXsf7DM6KYeT^ z`~AwJBUsU{`M+{{sjVn@VwtQAGAXpU4+3JxbRPtb?OL>=2p-d;-AQ-`+%2rAfjjbk zu(#^&8?L`~X7gBT-`dqF{fYe@>_XAD=Do*{A7T}C)qv0KTTjM!@%x5Jn zRYH)gc);qSaLcdr6$7{YQbQ9A>k=YbJ!r=@w%W@8qr8(jK4qCUgnzi>UxH?j2im2D zhe19jMv`KZW0K;{7*P~)XF&G;vhdU6#HTL; zBzU&Wf8YjRX-HhL(swn_7TA6ui+VboD#>Kb*1?g5Ys)myu|bWdASEkl;1`-=kxbkX z#$z2p5m6igK<34rF4NNfdHW<-3N^x{YK%3;M22JV+!s0^h#Y6!pgvnr-Dx&citGIf8b$2|jf#<<=KYB4)LvZocDYVmnYeD$u5+x& zH86h@<5sM^pVayN3hAz7Fy*v=p`bZGh0OTBw10|Y|3Vk9N<2RP3c6HU%cK6h}w9Wf&M^}eSe&$>j3bh4n` z>W9;E$ktz|ome%m`77mXF@8TVvrD2xu!^4&P>uYM~^}C{2C68rH00h=^2>dEPQ{wwOID_~Or{oJVG#^+k zas9WB>u=I^%o%H^Y{d945g`Kq3TQ}ZKNdxZSh@EqWISl~xJwfEE0$aP5BAUA^w_<} zX9o`s96f&Txtm|d4}%i#4g9F8`bWGsZ2lwmsT?CEl6^21T37h7xFudPGQ`FWV;Z`) ze9p)a9#+q3xPg5)zj9YY*`CaJ+xo2Xy8P@-Ycq1~1#NwC6Pv5Ilx8L~&+^o)#^mV8 zp^z|}Io8~XVmD^nV3xHi#a0?Xt3{`0605a1Cd7R4n2^qCX4^Tf{>Nn~%W)4Io_iM+ z&+efe5q)8&+`*m#oA=5Ee5V#&#||Fl*s&8?4!$M7gOmF?V?x9k{ug6H;K}6_6XG|b zbTE{HS;ab(k=1Nt`B+39rb1Jads`~aNlE6)7B%pVDVmg+AWnr?CxWVrM1+`^MuZS7 zk*6{TA(lNdJw0~UiRWKF{rc;22!Li@{e!+{8?gpv_Ye-w@x{0Nf32`)r_Se%ryK+kf)^h@sU(KRJeNgjUQ2?;CQ zyyOt<@WYdn*R5GGp_F#D{zO}ipYheWNaP3BgQNkRFpR=E@&o=b3UD9Sy2>L@nuh6B3Aqf)-w05_KpF(V@a)7T5FEuxNeST(F$5fk6t95I5e(TIU-#0dN6H>c0Dzft#CaP&9-;e0;wCWm0$+dLM& z16t@ioiO%Kyhnj$uuKU7pCRHoP!T33+8E3;hR{-J^CG1yCx!7uhsdv(l=DktICNE^ z&=iK8y=~8JU;Z*a&d!}$H#)kGeL6CF{k7iDe8c4R5!fB*a5S08BZ_F`*1I~#Am zy=jf7`UqCs9e5>!k9-R5Zww-dB!}bs@{t#tc;tluS*{y(jl6I*R_Ds$)6JdZQ`05+ zwRYN%m#yp8KXfK1JLX-E(dLiC_lDrcIqm|e<5F=KM6)2z)31EjZZ&0EnxCG$Z~)+l ztpEZL@TiRQexZdGaTn&#j=O+6J^oY1KYefd<>?>OX!SlB_wxb{RGvbwW_PR*cC?=? z*z`8cg3kX6skZT)H|TsAE=p{h*iY=FQv?9effkj46?D1-C#!`c;t0&Zc;teo_k4Z& z4+jru69m=ooLA)>yicp1r0YZ|E`zWx^@?#palNE^5*Mk#KiO=+cA-L9V>5=b7q{-) z`j6@BB8mzMckW~x2RnO*q00z)jH!RiOixQ@?5kiPan2-EGbOybd~}9c=shUR6)(R; zbcS$v()@?PwE1n>x8*N)vB)bT^EOXSvGJibJ;OLvkEtJKrguL@d?RN3M?PbU9)@3} zuA|sMCJJ*SLcBeswQzT(7i;OULyRb0>9Yc>goqR`*O^;ktw`YNb(;ORx9r{e!SqQM zIz2sy6_}iKUV(FS@Am6!fU^QzuiTE2nBBt&2Hca7^$E*D^i+8W*R^O6Qra$#1~GrG zXplv%J~J9bxQ=n0YRn1Y$`SCvO2y=!O`oW9)6-0uzBV*>Rrao3thKYbWAMTSIplla z``(91@fPdNga@tjpRi9jUM%9#q6zaK%d6yAw$eRQu9ZWc)#E@yG~eey)RvC}?-V5= zV_rCTr!eqNuzPhx(xW-T@M`1y)MEK(8EZV z3(dPHvMEUT4o!xqCuT;$NTkIPR-#BLf*)e!Eh8s&%3bBKI&jx&%_SEy6?^CM>B`cY zw&{Zhr{8)jcA!9x+Otdj2{YH$t!Ys|kb7%ZudbOfSvYLXf1-3?he$HLQ+MoyR@OBl zgdCCeh>%3C%_B>Im3*rfHw2Ax3JIacF5x4=J(qREf))racq54F|qYFg1hJw52tw6i9)HnFFws=KFBj;g_~PS@sdr~u#fGAx;D zT~c#))4)Je(;&{64T}+qGe*)QEJN^87aq#wsq7Rp;$RK9ks{cS;=>SyjGb}BjOZaF zIk3MY&4Wb_-G0s16NidQXLs)2ef$Wa{x5y$iQqM*gJTl|{a*z{d;scB5Mh;0*!|2* z_JZ#0j9mKV z$^o;Qe24tSuFAq!UMZ^R+CH$x{r;w=``kJQ*_ylWCVWb=?gZge_X)x!fLlo(mrd}+ z8`SEtk~(H3jQ@xzi?sXje<5TRasx$+6D$RAP$9LvIDaw##t0a~!PH<#zYY~aki|ra zB&0kfaMJ@tsLBYtuWD6HsxRt9Lpzmn%*Q2m1 z^4up%p)_D+fOeH}LNsEhKre7aG>4ib;(EE;{<480+ydZ;6N#v*-Cka~za_;Q5gJzA z)K*$jFwJgI|H#Y*o9i2PRd`OgHQTFeqQPiRo~0e~n!iCgj&YgYp|J!t7?N+{I~4|v zE@=gl2;QSX*`h$%5-?nz83`;Y@>VR+H)8Rm5l-d+YBskfQVJx%mD$0kFPkjFNhJWq z0Q+=P&z_N%i|GWi^VjwGOm6Z@T~;UHSDuR5bk5%(pX9UQQmJ8xeSyygUX#ed6(V@G z#W5^!Xkdy5foVc%@M73m!_$3z(>L6({+{E{zkCzq`Za6p6|7zT!yEIG%4HGa zZlA__T`Ky8*lRvSr|1{$eekvuZ<-yM{vwOe^#|3>eM4{^38S1+8csJn%nt$u(-l#=G(lm97OfLpSq6E1Nt zR>Zv|n4p8ExEDRNg)W+)2Tp*Vk3w6-6Jf+Vat}w_qRzG@<1N))e|VcqJfah5Yffuoe0F{N~z<%F$ca)RvFt6kNV3ExTy4 zu6koZWl6}2)JzZcH)RFcg=Hn#K~X^sSM;@>Ovy>!QaUu1dRb-t){=saRSh%gvQIC2 zuA#hgV{G(fT}dtWIsto1S_d<`pYQ25=@Guy2H+mjKA7EO#a>I-@V!=`wSek+X>BPW zW(j^GO;lS79vAMQWTLjz_@TFF?|W#U@^9agBh_8(YJxDm7qdH_;xR6J)%Lna?8Mtc zW39GpF)r@FC0#TMnKS{wjBFzxg(pV+0j{rHCdLKQ*Pot$oOO;6$4i4f1RA$i^KLuWeu)~)jl zdRLrI&iTpg4Ulr8YB$P{1H*AS)St;bHqb*Nk9v%QdsKot6P8!83t?&}ft&-_=`DQrkW~9k;G{bQdj7Y;EnDX7xk)ZS}3B)LobTkWN<+_=^t# zLGe0+gA+NL*BCPtm~L<^9pHf57`iil(*i^`x@E~*Q*b8d9eb|uJR89i))IS zQ&Nj3YwQInnL)7;VNq2RMK#kM=jw`!>S?cou#PkjvpdQMV6V3^;6bt864~Ko_IIwQ z6E*_Ux(aIDD+~J83BScYz(hDjwS;3Ium~U;#F0az<%ntlMCi=-Fy7;cYJn{YQ7w!7 zlO1YcgWR>gy`SlEtR}2?~h7gX_A#@9#edLF3o2az*cd zbdBjJ=~0Z~qR}{H*x(C=_bx&zv;N_YNOu5r>2Gk}Dg>b`LgoL$|rBqMDG$)T=Y`>qc(A zW<%_&+U%4py!SI>aGYo98iT|4*+~hnWYH`gig?;s7hSyCiSr|@!ZAXvEuQjW|v2MqHxBSKCZ(o&D@ScE3uVk=#Wo^j~^s6>@ByxL%pRisO%WCL7}PzG++ZmLn--2 zXmn^)M7V#5KMKC%F#08Uxi}06UJCvKL@4xKy!}{X|BPXvG3w@61qpao~i8?@*X*CFIXF4 zYr)#oI$ftV;5Hca<9r=3u3djuT-Pig)IaAx&>jw+M3cask)Hs!_9JSrKSo@ghgOA(-S2{0pSP5>oGk-=!hQxJ&Xx~WKu{E4=^(;tky;`7_gU;BzQU=Jx#J#AlKpG<)8vNHVP>+KpzpxzHurbHGGpzg?0m%ethZ%EMkhCBrI!1l1YAm$HEYmj zj*gD&gzeu3XHnuTVB^L5#7I?kJN#ee5WtZhyG!|P!Bvnbg#FTj!5{@;<;Y0^n}$zH z(;N>75~1CsF4O@hU=jNu7p&{Gl!sU%lj3bIEQF6jR^|Wf-XJJA`-Wy+F6(Lf2~I{L z)FT9fJ`@O%2WV?i$-;{v5GA%H!WZa_Z_MKbX1Z^kvuGNK}>WQm=-(>L5F& z9$?2g|EqEtMayZk)Ul4-h;%Co)&P)m$(%N>ddU#7q}q%q&9Qmk)F-2(q?YW=D~469 zYfVSb0FdLUE>v&IVQOEhnUN4j_P8>5;Y5MMh?jQS!-xkA4+{-J2}Lhb8X;T(CIYHR zMGE;S?3D}#P?kwH*Rd?^8%({C_BgEM!hYJ8Hi5ze4pH$&Kx054PpJi~Fpmi`QzZw; zHC&?ga0CX47&H0WBx_k|WnS5#;zRq2j@R$4{wTWIUKKk~TRSjN`=fDdYi38pkou1N z#<~sF)9v^Bc4c=Zmo*IzHZ=_pJv;+g9K^ncXc?xZ6*#ZJY3_hiRHo%DE5?oGCVpEU z#x#<+))0>f9LBTE@Jt6Ta7av}JP03vbzN3)jgiR~8+bU{`JZ3t8WTV#TV8)1utuEr zl^BC&dxLa%94}O)>iG}le)%sLn@LI~KPc!KMQAV<9y&&izByJKF1Kd0In|tEi?`YI zDkHiVyCYx(fdN)xZQ)pkCFHhJ=!?l&)VI0mKyA(b#?`wk?c1u0^VEOtDXXr_$!Yv< zXMNSR9ffS?x{A87?5y>*tZMa!?6iJ+L070PKR>t7UZS>C6&DrnEv~@2*zM*z5e{Qmd&(5W^}PyGODTvD0ho}gZ}E*p5*B=L!zLx9SW`p+ zAX0H4JCc|MH>>f)%^uDL(LF4WT~XXtSyfuRDrG3vmbN8#;%G@nMMY^@ZtfO~Eo*NgChx#kZ*XI@Xr&i`>6^;~UWfjH^re<~*7WM~V4Qn9VleR~e+dd)=LwqMb z1>Yef_sU6}A6axAURvOf08x;jx%SnE^fa!yaM34&u9ems80extP)_>NX@{=#Z7e8P zlL~7_Ge+n^-Z-E&B32F}jAm%TFkE8g5P`>jc;AEjFC4kRu2y%^xyHO`oC7pZ(u8W? zIqbkDqJ39>%3*1qQx(KY%MoZFB%#6%*aRxC18qFATP^WMA)hH|TC{ArS~7R2dqmh# zt{gEP&{ICTdAhWquz^PN=lG=IktZI38)@p}e8q2+&*Dw`iubcG@s+Ke|5$lJ4u)rK z_tOAd%8exOc?fFjAQiXfcri>9Ad(YlkI--6c@ywBG(xzmC4D0y#4MRHQsVE3nPEVU zJjIBOe>#vo#rzU6a*~PNeH<3iA z1<4+@znE3+{EcoAG$5DA@!Khq;eq@dONM|G~4&gy9+Wp|PVhsO^)#p2YanA1(}zsQf2N z1@uxzSmE~xD}0K>xUNTbBs!bUUG4Gqg$#{O)^*TI3K?=W$HT%Nf(;6>git_$$Sb)_ zhDK7k1i*FI#ljBybby=K>n>|rv3h#CG3fM!u(0S#>(DBW4DWWB*t46H z1O{OrVF#9v6cv&Ir|T|}qTCh;JK#^h^POpShCO^!$BQtw(Mb+Nmsgh!6; z&+hH3uIYiN`we$A?5?oyth0|5CU<3K6>akOk0{DfkArHiSyNF#@f8&M7en&8=5tag zd`=`Y=;W}Vlb_NGwE*Y$P&>Je&L(8nLwqF!#`&H&v#ieJ?{0(h>fw32rn8?{HJv@# zn+K^s`7nRZg$|>Cx?jid->I!UeJ7fN?|9z@dA-whNVD4YH?`}ZizZKN}^eWA} zIvTGdEn7_gs?7(cD@v+br(Kj`0YJoG)u_BmQ2GVg%R{e@_-G9gh(jV~i5j=|T1Rwa$d5#?X6||xd?fw^O#fygyMs#J5Z9lQ6sIt5;r6QoRz;DL{Yk1Ze zHa0jqHCVPPyAZS@w@jxMcm98(6&YJ+x9>mr2uuE4v_j!@mGng-5_7BF+a2j#oFg%* zD2O`{)i_k|ps?h>D6ew^b|ex@QlQIiFl@vnQyVxwin_!iNi>g|3%ynOC%O@jcIO%IMGpHB0l4Bvtv3dhc291{wowIgJ4 zX_oJ0S}u)Pgk}rO%R;JeGB;>8De}N@5<-=d$>Y0s?fzhT`h)4){Yx|RXNn40YgbF} z`VFHg!o^cnGg1>wg*kaRE%S4zXRrk{Q;Tj3`oE1%bRDFVXO|Ox5+S;u;yCX;1P8C@ zo{Q)dyJQcjC}6X+OW*_}yB~b8`v%&f(%kHV*y!}!QeYuL)d&lLs&Ooo@1RnHT&vBm zLqj{uucv}LdxlfF+Z^qjs2n7xP>#hCV8y6|6~hP)6Lv$YpB@nw;Io3)PX`XhDZ~l0 z@vsGfnWZMqi$#Y-5hs>_KshIr3>JddQRk91wkMYgsk42sUQD+(9;j!x1$7qp6jfTL zYWlj=pZT^H=(dZB{q1e(nPehWEBc#QQC11zUNJh-C_hfK#_UGy4tMYr;ySe#*QqMnGC1GkUF|x_2Y=!k?yqR$`3YY|e_S8I z`B?%@OSN&7`#?KOMCfopLa`H}uCcDJ(8N{R0CzZ;ErF)LY(KX1z%=x7&pl^s&kHXt zD^4!=FE3?-eVzTI8%AF1%FXQ>3<_62$VpDjhzPNzW)bw0SJDTFQB@$E#@kJBE#h*k z%n);jTU{W26p<%O%@p+K8=7J=A<2tLkw2L8V#PD+N9?-qA<4@V4AU1;Yr7FSYLO68 zV?w|p|*gK1b+|Th?M+AJ}0F6;=)n_kTZ8ZF`B#{ z$t_&}(B$Mp>x))BqTbosS-!2VZhKj0t9s{D#%#;j7khiZIMyf zF!)ljbs>pT9twx-DG%B&DzP z!y2##uG2pTHKF9*Me1~3=-FLrMnWU1LV-nbRC?kC)}Trk%(+Sa(Utr5W$nv~8(8Tx z6daPVCQpuIPpZx0x6wZ-Fgm~K2++H<#YFeO~y%C zi?RI|MJY=#g5Z;?AJ86BMK;9X5C9N(*7Jiea=5Ri1k}}>1pupITga>^)S!0DpS^~)wTJhTZRh9>`xG^F|1!SM*n&3qnH=8MYP1LA+GbhgxgK+tQA_geeId;;hi0<@bGSYtIibA~Uv? zGS&Ifiog$qIBg_pQ{3GSfz2jhbdxsXaPpBu-2 zm5utYQoniO@`HEZeRn^5L2bVV4q23A?e9=*Q$9RU4?Pne3ZraM3FbIF9uaR% z@C0;-loQ~=bTHS9Th6Fh2p&^q)w}Aizq|X6jd|-VnXB42?2H{Qu{AZ>%UheP*}2^H z_UiG1rykgIe@|%0dh@~EEZ(XeyTr-x6`ZpqsL zF`_(_ekdaC;aFwdk`67oCsf~_k|(y6pd2atEYR&(a#3mY&sQ;n`X0(n^vvBMx9q(D zI*^Ec9MjeyjlVMnxs&Bxa|l(!!;rTE&t%sTCGd{gE!r zkf3GwBL%#(+$JX#*Ucikb!|~za$3cn=9WE`=_&8l)s>djuP&|c?5wZv=%{BsU4{1H zoSb2MVOMx)V^(o*W@c}3RzujIS7oH-=B8z=n)|dQcU4Krs@xLbv@p!6ZUGih)74#v zP=)f>a|osCP$E-mz4MWDg#~N8>%?>~A1CFB^JLfqi=d!}!^UdBK`G!te$f>Q1Z1cW z6C4mc5FAteu32at2p=bdgT^&@iSj}#0vn^=_F`-d2L!iEU}GkqY;Hc;-OWE6hlU{j z4mEnWUen)yO{@6aJ~1>jLB9lz0w4DRAERPAP%$pasJJ4LHW!})8-w?A!p6OG53oNF zHYO&*g3*T&N8pT&(F@EP;|^Rb`U;c62p7X`lW?)s6&G``F~}BFb}*(}D$BOyk0hPy zZ`@IqTXpN!t&RU>&5gFEHu^|EzR;oOuyQekoU$B3Mz`kqJAsjVfstX~*D$i;4ravN z&HX6uvtvNa;7q!>8ieXoxk)WLT82vel4!Yj0WA|&z7bft7g!dYk3MY>LoH9|0(M?B zZ|tiBG4pgg+Cg?EN1uh5S1(1(;5QuAmE2#%OMvUJ79|*=r!?q->!Jlh+m1%FfD&N5 zE&>*c{Z&|Vq&>!g@Rn`hg7pAT%VE7<4eMc4I@XijU*dc}A|8M-)wy!wWXy5s`80o4(eFAv>zJql6;eGoK(=S07Vg}?+P*X3-W{(HK=WK{p)sgdo0{1xS?FsonJA`Ozxl_%1 z(;I8H(Tc*d@^Yj`XdS%b`cJfDo$t|=5knV5r?T^wXwNBv)C+iUDb|~f#mD^ zT6UM`)IBSB%t~`+F7cRI1EmN6r0RK;&t`)2TJYBi2UbJM%w@5fqZpTl(MZY!N{i<% zOU@`wv)5X3HH`M@R{QMQ&g(MEdJi^qb+%PKsGec-PqAw^a}LwCv92&Hvp_JI7!NEL z*yYdWF!j`}mq2AF_a9X6V5_cC|KpWC`*jZUI4{NgeaAj_8Wo2$`z6q-dMpLHhF`vm}VmUdfUyjGr?_C_*9#P-85?Qr>>cV5{ zSb=!+OY)emi?)=<u+x9 z>1nw*kC~fJJZ5^X3y+x(XgIh44WGqh@_d&%km}H2F_FAMJf`!+F2-Y$ZiEXQzYuT$ zG$G)io8h=?QQnE6?c4%j?PAhY{p~gP7o)7dX#L;b=sA$~<@;<96jd{}nX7x4CBlO(!pd zrpe3mPGIT(cU-3ns@AzqplZU@Hv&`tuX3FiMb^u6ojR_T-G2vQ0`|jN9Ki_xzj2)l zc+iRK)bXI~{x(J>OZiy0gMivd*zfD?)vh?ip|e;JSQjzv|I27({b!*O@X=xOH?vz< zCHUq~m+;Na^LMg6tQhy^#69qs3q19mY=%FlF1Z(iK9BR~=9kD?OojS{AS(lsvrtMSGFc+wF?URK(Wf4nnCR}tZ+byNI{g}U zjgDS++34u5#`@~&y1MG>dcb%y0DTX;`(MVWC_2AK{l(PA=j!U}SU{cn0Qy3x3ijtd z{DuD}u zSpvgiLPF9kw(9)2g2XX8u_0K&XgpX zy!DZmh^FMs?DULPV{fq4Q}0(~#h1iKhExTG#6(8M6m6<2*n!c&tqO2=HzauY*({de zowTZq@qCFwp{h&nmfRqC<&)i#-P+LmyB9{EnQ3bn92_iSUY|BMqjw|bdKA5fQI#Sd zV^6xh<)n7U9DzT=Bvdtzw{o9$5x?g$PuPqt(4!t*-=8-^lT6CYUS&TJ&Vs)0o%!jZ z!luZO%0SF9D(c<3LF{QP#&8-t9tvv?qA5`^gQ0SU91PDh>^U?$&;*e(P-J+f#2%cR z(c_vdVUz;}aZT~@u0CfRo;B4ej94K~zD2CCf4E_Ia%PLEJTxvbv#K?yA}KUIJt-+C zGo8tGg{4hfM#lI1`BVq5E)5QQF+L{7nh+fcl^B!a@NpU|<|(x}vGlxf*6H$Egm+`%}sP8|86@ zgEbb$$-|mg!qu0@WHK5}siqX0)fi`tO-iy5v#0GBWfyfE`HQMZ*)5xTd&aHi!jaat z^@aClWF{tOW+rFm<)Pq7ZpJe;1=ZCB=A4z$#j7eRR~1EH6&)5H8yglLt^UFs6=ktR zMVYa`k=WnsT=o~?^?I<2^S)xC++|AWtWSW)tzB$?HD*=#5@D}h_SfpVx7FD0jGV0W z#Pp0LcAxX!(q6wrdrNyvcGxf$!fugY5IPqYEp#p{nshGM{bm-;_CS{LA{!?0jC66t zA?h!7%VU~&??7~H%n8$>uNnCToF-UHTrnMi3U+-2Yr&(2tvoT{0-~dSKR=?wOD}@y zFxTUl4X^TWDNM(wISQMKNVTmjAUbE!o*YI<$qw26Qix9WSq9T#*)=8{9 z`?_Q0jTkFcSJZ1&%Tzs&jBT_7qq@xpVj@Cvc+x8~-wbC|MXrZ1syJ&FFNLb)@I|-^ zV~oWpP7`#Hc54ZA#9;=fxuo0Xf*Ujxz%`86QejbvqgazL9XSB2&N|=vEbJhk*Rg{$ z%#z+rj7rf~4tFCl=Ii*Fp*M3KGk?|&cz;iW_c{&U>vSC>s{_2hEp&{G-^HHjcf)XZ zhCd4(=V2Lf zdS1Uz{eQglc|?(m{^7u^-G?Xd!t;*%^!|^QdEQNb-f`dcdHp`P2krS4F8%BG;~e*W zm)^&*^+#o~>){avC9%K!~gilE}NWCUR`};rmDKUyrz2QNKFm8 zo__;e_>JH+P)6Gxj0@6HiV=Vp53VQy3LFmHv9-*sKE0haj46+P+Chy<} zG4@DYU{Vha6vYLAm&l)kIBR-1&p~EAESobjEb-~-@h_w&#Al>i6VgRbUtvLP_FwDi zKzVs;s@bX`A?)D!2`?YK^;#ch!cP5MB~g(YZNSbenR?@MEoSQ z9~5xP3yYw656zl1Ae^7aecup1e4&%D2%~DwUFlwaIqiud;pvvU)2;p%%Qd%o*&?jz z;_dRkp=UFHs|BB*&>DKAv%li6EEQvPs0NBvUCA{s;%#(@pi<^>RZU8ho15E8H$Q8D zKN*lw+0^1Jm{i^rKDshJ-C|BlGovOz>icP^IhtxRrSaZ3qW37Q;!2{;cngB#a15{| zZkSjjDQH(B8ztWtckD|bO-$I>a}WcP^C&vCp!0^C!bat&G*et^YMd!;u7SOT-r+CB zE8kJhv(@%m|CJsHDg>NDBH6G4`Q<7bq53fxJcqb86fXlMy$s3IW#;@S_H-w)v5C)kZULLLZZ z?gJ0vXW$9#&lB7$gu?)KVTBZ~g{aZKHgN_*2TA8O?aj=vSTZurhKzV?db%|}1G9C5 zzq+XNX(hYi3U9dPz?B0|{#aDFtBJTb7_Y$)@WMV#ZYc8^VTA)jc)ZQSJ1hm^=dLDI zQk_!3o6COwS5`*6rMeL2)s!m+w;zv8h)u8_j!sMC8_Z(rlH)RBl2AC9!fS~2{|!r|mA2iPou3xUnt zf2{3DEK82GWLd05Ns)2sF$r1CALh~em01*=qCdn zGMuu|IVjlTH2|3BS+Eo747;53B=q8P&gXvvtac~IY5{gXdc{$6yG!)PDOKurAT*^x zU^59O7x<20gX13e7^}9EYLiJ_G&Yz)CDtq+eB48-&VrW=`6^Yodx6N-i9aAz&> z`@8HZY`~>(Cms%T+L_%N?qt3?^2B=9N`D2<^&@CkY~BsieTcLWOG8IVhW6#KwKs)6>1Z)6=!7K;UH3TFjss zGcZUcf&)Qm2w@t)LTg6dI*I@eqGcxv;B-|h7y%R*ghfS2o@MR(J9oY>zdbe=$F)mX zA8DTVUM+e@ngLO>if^Nk4S4s#*ga@JxF*4IS;z%ix^<99q|qYJ?|^_EJO1?inEdOp zPkU(>5D(94@D^W$wK%vayg&mH2Tz5S?|TaQtH8c;qL7~>oM0`d%gc-RmKGM4?j8U0 zl~?|yf6IlVM=xwa&mrj9zzckYB;@5^k&GW-k+qVu5}}+V*mAUN zZ>2>=rF)C?LiPY+U*@~S3Ni1iuqX9kr8uT-5_^I>m>=DNL!--`3YN|~ zp^f(NX!3TwlPoo`PuP#aD|TYM3O--qZ;iqA_xN?9J&H#cUB`T=eG;y_6UE>T%?-Hh zLZJc!2#SNiJ%w75uibIgRXf;<=l>6L?*SN9vHg$Vxpzx?@0)D4B-tdJ-U$iW^hz3p zgq8q-5D2{^(h&raE+U8^h$1Kisq!p{qA2p7g>FG<&#uoW_*B^af6l$T$r3^F`}@D| z@3(L=ckj-fJ9B2{%sFSyIdc@tE90ot5b(Eh~Esg#aJmo7rZOyG1Yt)T}>uO001tYiCbjKIjyE#ru7&_oLZCwweDH_xp+W z`_X;W1^Kymkl&3<+3zN@-<=cmj$%#|JynHS8b&=YLJQNa5Cky9?wZ6uq4)Ao=&^E?~5I(27sv5HS?R6g?Hmt%d(A z<*XBP*4;HH%4EqrT&bwc+eR*xVGw^o8J)j?B1=+C7G~CMQcPT@6q9H##W?(4iYZv` z*X$M>8y04a(Ze!!-ABd{b#$~E@uX2Y#>hu}KDY;W(S`^(6~&hXF{B5%DaVsqt5cA1 z_`M*rJ0?aShVqN?9zH)NBqTaIBqZk6%LtQCq zn0S8}-KSB6{2sXf$1~5NW9H3inKi40sdw$#MNg~ov_gFP9%dfeVx4&?V&1%QC}S7O zh=tsEl3N3u*h@5toe~Y6vzKW4kpK}Fr&E%27z?Hxc!fm6J_KF^z(c?b6sN;5L_4Br zdtHPY5z&%RlPIeQ3=Bk=#&DR5DjWigonbueZ1%g#G_M;CYIR0Z-{ks;`>UE~#2R(l z$wAiMzLg&0uk#DmrshT`ROH2W(*{PU?0Y{Oa+j;v`(fmJuYqbGQCZ2Qz$(&oh%!<& z2a(k#swwX0g}LZMaf}Jjm4gG;C=2n2XBuW5mivrG*4&tqs6f%3zg{X0kArNVO^8FUYM(D;I)RuE+hc z;{CB5v@#O3lJbuh?~m@FmGPjJ`M5tuyg#OcRz`wWW`TmC=Oto^-$g1J2cNJo9>cB+ zL2M5qg6QC8tPW)dBLYcW^U&(X&lKrlJm_IQN`tqDL=dSo(%7&BQV-x4Fe-|t{R+2Z`LEQCH~J)XS>eG1A>>#9!?>x#1V>tpp^I_=o| z9U_8sai}k_3z?88jh##wC(490-IA4(Oqlf@@`ivbOTnrlCmmMD`Y6eKSQuTAA3~(J zK4ja3jT3bP}|@*samNP&^AmZFt; zU`(oU-mt$P&m73xg%DYad{|WngR_THfCDZoJcB%aWZ;6sNOZpacx^vFY|3>WP^-=F z9W$$E?ZDN&daWK<+jCY-@BBgL??(3NGje=cbxL>n*zwMTH#9bG80@&~#w0jtV`Wg9UV*R2_5mMc}Jsjjba{-4SZ4lV$3xoV<8NGY8BBYbQ*)w5- zf`{eTSlzqwTU3L>Ot1GyN$D{uGb1fWIbo7h*`(s)Nu^GcCn|E%GBRDwhg#~~Se|?D z9_TR})^zaJO`v_`;6Q)aUB|{mh3mATYF8%*(sANcSdsTwK>Iy%NI%1YJTOrzpFkf3 z@q~yb)sxm<3bZC!+S9u4N8#2)JPHy1-Vk{N)Y}^~n+G}*9I8pryDu?qX$JTxD!*Px}9T z;n4CCQPCsITMDYH@|(+BVvNJehvZjpHkKJ<%8bUc7+ipAae9oPT8wb1uW+(WLVQFx z>Fiux93AM~V~lXV%m+#(8t3?L9p|D}%`?=~TO8-&Ade6?m;9x(SP zg3CxjtPH+8U(v)i+oRXNF^i6VF(&X_jq9^5dRSRk<}2Fb4!p(6AV%cRVAja+y!0+Y1f}dh>)hoi54^Hp3KKaw@+YlpqKz zcO3VQ$;pd3AdU=kqUfJ7eF_saUwogq%UQu!(8(q$Ra|V0Q5CIu!C@_q+9UVMGMtt-&-iENo$6aq+EVQ58u^6;aaP?X*Y0sd46zEB_|! zkE=`-svus5Q+Ij9UxQB%!U2kAb&hoW4s#}Xor23iIa7kB;ru5s@HDCMH3IV_eVk_e@sQMgynte9~tgDVHjh> zCio72q`uGcgkBY6tEY#sQ1joRGD$Lj9x}bUwO4hwEYqZlB4>Yp=c0;9rmSvNwH0|q zel9M4MR^s_YayN-`xoQ`4Y$GMg##uPYJ?IXYa16Rnfcgtr|cVs~!$R&{OghFvh1)=k4S53o(B2qU{ zuhYTqPq7r!& zvnyXl-6)2@l=S#cPQlPbp>uqp>4EJAsbaLg@aU~I@A~-Tc|(UX3tx45Ok+`NYZ31@ z8pMNEh9jUh!JMA!X)5!>ywdpNwC~Vh9{X~PDP(0HG^Q9|3V~FCQ?8-Nmi98N>OL&7 zh}=f%K4|qZj)FYBMHfnhnYhbRo0c9Xt!jMMjKn#GwT-K5YgaecR?Um6EghQI%{*~L zef7@})CgJ%FjQ?-({PN!Y0k|wppxu)47rPFNTuiYBX_D7Ug3?HOUp4_crPJ!aq z4*oH7?}UoVyxGRE%!iBm%_(pyV#f;$ebvU4*p@(5YEzFupCyHX`lzHZ^)S65r7Hy3Y;aZvCwCr9`JVWhT@)lr^!-l60ej1XtGog7pd#0hF(I_oG3j+utwe$u4upX|qDllbU#ujBhAMzL zvJtnQ8$ln%)@4&Gqq19S>g6|Y-MR()=EX$?MN^iBt; z?Bw7iFkeTd0<{!lQDWl-)R0_5F<-E2)QIT;l6Sa$ZjN= z-}m8(VuMnWXz*LPG<@Y z^mB0pXCW%Qu(Uxa0g=LcNxMz&C4UEs+yk}~ap@G|HKaML9enM|>S-;_HO<4P*Q^>; z!~bo__R;2-Sl)e0T+9*dg3CkGaWq1~=rIcgjC=&W%H5B z#2R@2V%{6V05{-I1c1!JJXdS{yGfA)}Mm| zsJ%$~RHj7wPCtS7AND6DNYC^&X`-T{VxwY=(Gds{1>P4UH4x^5_~<-+JiNq}9WoIG z4+fE^P%Ab};CyNSWUUt5t;~S2oR+G9xlXdst&Qi~W~x`TZk$KRVWF+dLucN4kNL;+ zTE_|m^Q$aZ=o@SPVja8Y=j!5DSlII&HlVPuaAifIews(2r{_aqVMNDhwx0lI=yWY` z=$r%Q8|R=HM$8fD8Ldr9rFdw7IF`jugxuqU2XHP6eVo9dhf6&uQT`-xPuUAK_N6h+ zWYQX-S-NrK(x>~>Y}{B~|IcpHS}<+mglQ}y*PIUr0*1AK<2R!&;(0Qd?btR!j|dAm zN0P4`Ih?HqJqi{j9yAp}GAWP)*<#6MJww$xPj9W(&(q7p$(7h7APw8B*cnlJ>kYQu z5m3~K2qnz2_{|$bJx4dPnzHRnAM<=^AA+JZvj=@^vaazn`6 zuY(koE*&BgqPo5!6*^uU*K0;Ts8CJMc<9o3?DugzY{al4I+MIj5fXXq!z1vVv_pfQ zlFReu6j^{`TcCePQSMLXXc$*Te9f)hUwpyWe1R3ecu8?FTsRm<;YrA{Ogv3Qq-!{{ z5@sl3&|un(Eu-kc(jE=U7YPggBASC|V4&pZp6;vkCFmq>^hUPfQGDjD!{-Z}!d1Dt ztgNiCthDU%R{X(5`G;f1V3!HwP_|9%dmZ+%iEB@U=ida=SgfXwDT(0d_%aVS7blqm z;=TvE%axpivc)gR#}mv!fIoKn#Ifq?j05rVY=#F01IHyNCBzdaZHP92>l9}nSS!x9 zR*3cpBA)1vism8BK_$iv0b+U+;9&1`5%61hvO%7lu9qW2xZXcmuJ=bGiw;=5v+kk# zqYV$$?Og4D|IoUHz2C0CzwYfmbLyIJJssz7KHVCyjy)7GD1a@0EMUZ~>**@qEhAVY zXE$z$#0&{8igHgBh@YaWsv7X82(!aq0ABoPcKRUZ3nOI%CIY5rODBg$$NQmML<)ri zM>{Pn#B~`h>YSi7Ng17`jCiOxfO8&ToB3Z$zW#bX%K%XYk5*8GAlL1znlvM}U20yD zP85~P#kl@A>O;C&73o40#1BJ##E}gPT>1wo>HM2~!Oly*Ls9EM7AJNZxpZtCYwW(a zK?AIgiVnHdXkm>E=W63yl$uY73Qv42dGku=h<`=AR-o?VtaycOGve2ZolIe)MJ60{PA;QpIdTMFgXelxXBdLp z5NvPAL4uRLR+u?OMvAz+MsK1^NvH85yo;k6W(z@xXlUW z?aq*(qQO5Xlw(K=;Kw4?oRcFD_Fs+|{Pa#Zr4wPr(uB<_l_?ULmGhC8U+~9SrL!w< zV_0dKz^UGxzM4>;|@N+0cm-QD6KK#|1YTbuOj= z!+XI%pqG8+so~+Nyc@J8Cac zcFZo6JP9Q`ap34A>I>{+Ka(1d8AxN8EK&SPooRh9ZMBN;E;hd?j>UII>=-_E+VERH z4E-OJDb)`lOQ_5jSuxd70^W7n)Zsfu?65C)x;SDH>m0hq&Z)*hf)iB$qSTnL1m|ZR zqt4>&bChfsuv8G61Eh;@6DOegiuk(snzwIne)d`O8ep8~(zpk4?p89m$7HU})XG69 zg%ck*Y$yzwd=}^A;OOAwI18h}S)p`BbSWT;CnF0N9BfJ&MMs6RV?Epz6iNpLzBxKK zxiV)*=c*vYeGWyj#gaNzyr}FhQ9BrD8{`>G%|=eg#&ABMg6s zEHc`w??jOxT8azuD#^UW)_gKAAxo#bqu}<2hNo*g8_IZU4r<>&L?zZ9+rKo|?*~0y zi9VAop11|#EGL3N!>)r88V<$*OqyGp`3ZLN{yjs{Kn>RiyMCpRkky=lgO8`L zH=fdJ0{js&4=PuXB?0P5=v!H*x)slHXljSD)nJGUm$^c=CdN>K@Q#w6Rg?l;RzAB;Y@{#SVLX}JDXXa-I|?R zWvI+9%O0MUT4DGl>{V4nZbY>96;(uD1Zok%trDj5(O5Cb0~;FS1a*KD_*N|D2w08{ zEF2>@+)3#OeIzHZ7cb!7gDEfPs>lr+fmu6({piFfNfBWY&WI1!>BDXil|!_ciX#<& zIXJgagJ`2mWA*!ity408ZD3m3z|6{FQBgyyG8@y<8Z)bgMnw&)G%qaH>x)y9a&wc| znY_Hj)MA~kIF*l19gvyXn3|iJnVUK=GjpJLWj@v|Ev;KlLPCxyAwGw$(ZiOD(5Ah{ z|A6rcJrevWnFqAr=qK0^i%v}FWN7!%A7myhn!9oWnF(Xig@uR7(I7uk9+TLB2(umf zKVLe`D<#KIr!wjdgZhNwdyp z7uOMYkCz`N8cNnph!FP@D67U4LUImU05Zdi6qd&ZGshE`;6Y=BPU?lEqi^KL%rmc@ z$G>!Qp5S!z29*+pQf8qP7vjwzl`v-~cxyq?Ph%0klgb~06b(W#mocO!T{*F{DCV-JviHre3nSf0aqn(CWtr)ri2DBDTzqi zi;=5Rah%GaqPSj&ZWA66?jVj>Y3_9dOv0h?7^Tp0-4;GPnZ5c-`O?8tOEOx^T~AcIjp3&u7p0iF*Ooxj%CNMO+?O&Yl`#)(vTnjg2zI#~auj>+Q(6xJUw) zPuq>65_I~l0=C$cnQ6-H)-BgOj_wl_(O0^Sc>~c4p+`ylJIMEP`C?n7biD? zIp2<9Z&xqD*;#1vbo5YSu0Tw%VmZ;B$>IKm0L8<>$+<8mXN8kJp6~FWsoNc&xdXPP zhn4hJ!G`fPlPM*|hILc9sjR3VB`2jvX2u`%fX`p(0eiV7%&wPgl$faUpY#kfQm`;| zllcW(A8ka>MWF}(ytAkyZ|k&<&clT5!^MFr-voK^HF=3rK4ePbGPy}!%+=S(NJR)0 zr9aPa8?x>6S*93nevO%0TUM;V(<8YGkhZUgos#(9aI1{t&t8eSRP_1Z3T`Hkef3h= zXE7zo7Ub{FWm@x#J_&atSHu~Lt1~!qEdPdNURW_^r1@QzGI9*_KEJd74wTi#6`{bLEg$H82K;%d2X#JB`0{AJ5YfLzlGX_W>T0X>la>SSw zXf{i^y5rorop@|0SId55KZrE|w~HDOwy`Jd&-Pz37Kn|mz4S_n*kg>}PH@vfXb8E}0RV}S-a4X>z&-UR4 z;n_HAe~^aBZtTI_{dZ$8!+z{_=SBgY!FU?c@vN&J3gsDv)YQQzlLirvp=Wm*M1|rY zLiR?kKkvjxp_FiwlS9Xt5O@3bV`S)HegJ`to78u&AEP>T86&l~jgdNTG@{;xpftTn zW9@TWcVm@o0cQa}OtG`6W;Pt#cQ*64_I4o7hUQLm{d^JhdaCEV`eaFyD{z zJM$_z*r5P}Jh%knfw0eMA5ab+PD+^)!=HE{uvpsgRK2fMQZCL4FyCK%;|AVc&!s>Q z@F_}mI!N8&DNa~#EsHsA?!!Jc_d%YCTqd|$#ZW}B zWu%ht7*68B+0dQ>(Nb?MW~@}>TAT}u_hkO?1C`WSb2)lL^rRAN@11U-2BiP;i`WM9 z3)81}c`xWF-c?L98k3Qj7pq$i@CP)qQ#?~O(c6#QoQuEAbe!#DtEwp9U>9O6kI&EJY_gV-3vEyfTR6Q9HCA} z&Bb7tagH+Hk(@Ognw+sE1gik}?#sAAgt-dZ%M~~DdMe>jkPO|CBhG>(G5%hk>T3$@ z-ksyR7j-Yp%|TVWf!ox>Y9ibfd()DTL{86;`Rw>kTTtl~QB0D|Y9QYG+euRP{1w0I z%+!{C33;wr(+bA)=rN`swJv0Ah<}uE@UHO_+6Oi58lN~UMinzPbLrWs)6PGffdg3b zYHRcOW8;p`nzuSOx;}SOS?R>wFnzB1xiHPZhLKzPHSHKRW=E5sU$uY0l=l|Q`^S`9 zYwJo&de;?~^uc_K!X9%w{{|NV`>7<7d1H)voPFx=haFiAdD6s{LF%`K)CH9nt%}Ib zf)uIINzsYwD0QS!r+~4Syrc0}6J~LnM(I!ORo-^syjMD8Nn%gp3q)~j<3pQ5OF8~GqaHjNW|RmOk&t7;4HO5P zT!ySdOs5;YA&wWlZ^&=RX179yTK+J5k|FW)m+W&47=;R7b}>wwkTxtYYnnD(H#i}w zTec~yA}vfCKWALh*wFBzfmzw1amK954D&8+Qmoo3SZhrfXYz|EOzLUW2Mr9;gzI#n zZfiE|8j@Hv-82~~ z2t*ti;Q>V#4h*G3d7Z&2llG`eRyC#!?B0D~N_=EOQf^vWZcYgE)A7h}tyMKpnJ&2vrYt4EbQqg4BmI zc1dNS#1^E|ahMBseC09+GDRSvz8;CqjxQmeaFLc0pPrB|+BadJ*A5@9R(y2oYa~sL zmoz9KsY&F=3v-e7E6HmL6I2OB2>EjPvVVz~7$Rb&#tt6`qaqsmR61Aod8%jMozZb| z(PjLr5*j3w+?s5U4VKx%W6`u}Y)M&8QMgtc_7LWRjD-lhMO}yw*;{+MIFh9tPGE;Z zL&nc?bil&I&!Z>GsnooK^#Lf(eQ36@P39ZS$vhafF|f&O2527bE@g;g69&d2 z7eO(YG3@Gz%!&R07=fKzAmXT_xcD^cygG=yG*9iN;Vm$Ytc;Hr{~Eh>Ga559jcjsk zMO+*iOvc4k#O7rhV=^;ijG06Mpef?s2c1HsZpjwR8P&O)51GwT722YS6G^#3w5GF! z|6^Y|Pi*6fWmDC4xfTiD!y1 z9tt-}%^evj2M^~LLGc2-MYSO;5Jbu^u7VxP1*P)wY?kWNE7ogZPIA_-3SD?ap)R6s zYSMrppWrxWy{u1lSXvfy7}}DX_4KjOfT+mW$heto#)_=q&}`FNz@i$OGOf64h|?O; zi(ryP%P5{SBXW81ZF|+ymH_rFJ@ua*Jvx()avRll^=jL#59~_v=HQdvx#t5G)6QN2 zh4P3eO!g+XZTZml?(G|)wDSjX1@AbD&&)U2=+8!8y^3S^@CUdBoe;7>D}!M(0y{Nh zD6fzsEED#k@ixiG2OO7zlatZ{%Z37*9$;&DsMmQJw&`F9TqPbg6dMv75r$$^UIu-H zBh-ragb@e7!9>SybHSV3Ppvx-@IUoQW@YT22{jQ@rNp7@wM+;NtSKf-GSg@nyr&Pk`5eiaHBSzcR(y|=& zyL@ysqo!QUP4=ErWM1UO0co_+0;|sm@{iZ3$pnb3r^H$70s)KbGGpnqC`R-Hr%w|r zBGEwsYEt3J-tqChQ&M}y$M;G#Pnp|ho;A0P&0~$JX+~pOYRp#bSiQ(*Mwi9Mmqkl| zSq}7B7t9gx9FJfdlB1)NlcS=O%?GP7p7OY5@HUBqX3ro}n_v(*qJH>0A9PQW6r__x zvGNmf2^SEe3VPI?^A`-++w#IPl9t$J^B|%Yf5C$tqs!(8Ptus=u>kRbnE5>MR0t0; z+9I!ao9FxCHphPC{my(P5BM0%RN60 zc_Gu`QOTUrg(+aLhMh2MG&(vBa|KH-G|8Qzk`oX;2xPY73)KhFK&@)J_`5v{qAWfr z1A8x%g^PxH^cYNvy1ooNl?Dn>2M2fVZU5%!tgSb}Gh zF(xfd8bJJ;>zBhMXx+f3VMEQ6`Ij1om=E4Iq&WLwgw;U{U3np&e{Z~cLU{+Tp7=Xn z9d0fE6JA{?t{&Z^B{M58RMk{f+BdcPcz@?XUJ-@n_Au{|nD9__eBvsjK0C!JHx~^_ zst(h13(e_SloeZ~X^o8!F3Zee&xQJXg{u6+ef)yWLn9Uj7=yJjSi#Kc{7B1qoW4ZS z6qCeRVn8opq99rdMtu{VZi4SsT_Y;mcpf_ok0iTmb6=e zyAtMTynVDViqk3Kr~pDl{J=VRBl4_$o#=aO&O(mab~)gL*(HYoIpzx09c3ino`YlN zhxs;(ivI;|j1XIhtqW=(@`(oiw@0&)_C+O5AA*41yq~Nm0DPO2hLU}X*}#v$TYKZJ zKqG2SZ+#p!hnJ=p#FCla_$Esv&Z-o+K=G|^$j3ofXp2!nFJg#;Vs=}-XcYg&VyV<7 zYg>Q`YpH|}luGzOTjrqD^?y>T0DJOZR_~%hQPV|uVzw|HH3jZaCh99<&|0S07Ta5; z95xKv-pZQ`QO4iJw<@`Cu{A-`D6#4%ucdeF^V*rChA@|zZ9`C- z#f#B`Q+zN^gZvzLiM4TEcp9n|paV1ooHoANnM3s5XN&29(oQvd^x{81hCF6p)M+*9 zbnnz%AQz)#|5FVO!xv6i$TyXhp+a{m3H%Gz2`mvvvHd1$lxUq0PfDWJ5eJ;?pFSGJ z3~y+lmX(!J|A}=Hh5*+I9k`w=_1|2n{|tPCWiDzB4X%hs=y0TA0mhg3IC0~Yi5sV` zoQs!EJZDB4lN49LW};wz=(giTJ5LX#Io-t9SW1L+aZdj^pM4KA+n&cZZ^_S3Ov=ws z3Jwcbslvikd`(_LLP0@7LSAy13XwE|RbkdTY!v2!2~cq!NU~w0SyQ z+B~)Qm-6tSU9rfpFgmqFAoiYuc96^smAt}kpXD8|{G>!wBP5{Cw98rzD30|Bc~ljb z`lNR|YQoGKjFSiXufY?oCXAQ2tFTPo$*y9MWLHs*<&ZR6IOof1i6W2*2G(>i+hTAJ z){*mGEHL213X!uKiykNdY9U#m*i10^yV{!E3RijfI{B(tKI@(k*ztwWOyHY*d|X{M z!P%vhs#V=fXzc0vL6!%EDHwZ*yn}fTiYm=>-bPEM97JuU9GLB0D1nrNXsMK=DOY0_+vJ(e&(n7x638!HUZR)YKU_0SVIF(IjDwG4%>QQPpfXv(WMQT z&ExqxOTLftL3pOxk}LIQ*RLPJzO zDZvRn`tb8zLw&phz0=)2(qn5PG0$#Yz{qO;i&PO2+(7ehU6@%tqoLD1O{Kd4H>*`>ZN1j>Px*wqxXs`)bKBa2ZX5o1GEE%0gKo~ zc-b z!fGmaoh8-cVR`=ES8JI)-*MQ=Z=BGmg7o?zl?JrPWa(xZA(r?*;&JTDzm3Nktknht z==A|l=yU-AfVZ@Pfm&Tapq5esYK{44u{ADStk|zQ$+AP*Rp=6t4x!t8DLMyrt_)}r z9khx5nZMV1XtD$JT2(K#e{g6~jWc?JaMj-uY{`Ly;m!3BOHX!%CO}-7p~Qls6M7wN zpWV?c$D8x=)a%K*TYQ~pnIr0>$nRF{BlFw{|IqN%AXTWUzzrUyJ+An6_XuWnbUWoBnfu=yLvnw~b^(VZB9GB<@B=My8t@3<^a&coQzMCv!v$csWCF7)Q=!jkgz zoOBI>D*HyIUFn_>*DEAEI92T*^4Hx9bt7*riMq8yqBJ|PrIshKvvjX?(TS}wk7A#~=AYf% z$`)A8uq*fa?9Hu0^39X%${^~2R_@=H*Z2TDtJn)J40}xB{&I6Gi%2$q#JXK9{*+_4 z-nJaJyv6rJp1X6R0t`-O7nw_n;4eCem>)mo2 zkbACh&8}=(WA2z6czsx47L~9Z?U(Ese>alFP=j4s+#xFn>~>kff;WGKHxutot)e$$ zg`(IlVyhThl|-$=o89>9mPpvY{%%iJjK*e_7lc_>dEvUXO|(+lN*3E>SF(sNlspF| zufI#lHkknh1G9^ebG8f-T1aLP%Oxr2j^!e^0l6j2+H!Yb_II@)S>n3o?_w>K9QOI8 zk@b5yf_Bp(Kin=upmw4R;fpd>qYMRyNEbwM?yS`T#=7wQ*E@NByAtls?^6l4@%mH) z8?R3#{0BG!uit?q|1Q1iZ|zkB{~d5N3zRFu^yfT3omm5lb$5RMyUIMcQ0Ve1X~bxN zjR%Ff;4L5ee}%inL3jTn&i4A9IolhS2V^e5G`La`(=<_T8;yJ?RnYU4n`nqFcL!DI zIvO!{#oQuQu;sq{*v0xF_@Ecz=gZyMYlNm~$GRXiNnEesV3{k{9wq;2?G0jXs=Y0D z2L-%C?SG&9u5HCQ8P9(W&iFW1YyF>b#ng~LIH6 zcVsYmf%#Xgt&Uh*Ux6X?ov1afshbAg+CEU=ZyrL4#TY$#l`CRF-DEX7!|6G#TyF1 z!~Kk?Q66N~1~X0YZ?4!Oy)*v?t$sA|Z^RSd&brOXYBB0nf!>}T-a*DI-QoungoUQ6 z{8Ubvv@0*_@kYbmnD_~6Tu66Ow`g@R6LmkncVsi*-*X2Yg?+)sjXfv4u7m#016rs%;8m>3U&5mJP$FJmP_+53)fkn zm-J)rV2#W`tbSS|%iHy+hAVA-kg*_R*o#m})w1q~^u;$gr$N=_MaB@;#q)H1ee0re zs84!&TN`R-U$V4xT1%!TEg-d-wPZUDT4%|k63w=#qS)5@dj6fZw)AwAENREy!Q3}V zMY@w$A1pzdF*2~9gX;Rz7$H4s7loM=H?&h{)^+`Cl2;4K21Bw?EpIC28?sXohQ%*6 zo5q5u_2ADTcG~089P>q}FVV*a4oZ-`$!djgE-kKi_z?jktxBUm)GPfZZIPrd!s!lh zSn>=bb&+*J{k^}EIV!-z$0?dGVBHb|BmKR6oqhcHJeCp94}#0IQ$kvnr@OOLptZH4 zj)gyjacLKIi*}`?82=s17k>y*5zLGEK9*?y2B?GHq#;*rR^|cs_}^(#^kJgYnxv#7 zgMY~c$$Z@4eyaU}lU7~%XbRjV=pbC}H%=hCjVHWdm>mOeR~ub*k#{ zwo`nSC5g9ScHWu8w9)xfjjlDkrJcqsd&n}KeSFs^$d{Cw6vxWV``O1eM5N;pIhD@E zxd&|pZBQrm^3KQR^l$JZO6N`7d0`6dLi<9uzXE$3kYQIq$iZ*6J#U?YcxeHDUqi~c z+59Ya{@!%Pi3xdx;d8*# zkAH$EVh>o8v@Ix(0gHxvz;fwc#sJ0Ji0{)!eN=po{l-x=iF+D5c&;}NjHdX)9d%Uv zlZWvX?#$*JFfP!$itnv=Mxz~Hi+8@Y-q~vj#!1~uPEhi{%kz!(&T@VvdmDS$g5sj} z&Q-n*cA>xE&SmSJj*+CeYQ3|OZ{j1wcYS5O(=kpJH>`J>@%94fHwC3&y>k$~xEZ}D zD1NZsnT9%l1}ZBkexy6-jbiR+_AHJ_x!WA3;FcKY#cT+CV5=JNgn?!7r~e=tf%ORk zzw1^lTR~4mL!&o=pNw~OvD`;iavtLY`k6iZ@{@1XqDJOv*b{jRZT+8WAO2p)dNHFR zB9_&$J~ez@c}ZAwbQp!gWaa^oD&H3F{~woqs6Xq^;$n=6tREZLm;bt~I6N{kyok!C zlV6#P8wr0uM5=cI4fn!b#*M}Pq3Bl3a3r841A*_fyC-p*h;~g5BYp^BL(b?-Ho5P} zb7Mw|*lUBY#Tb+e{Sm$Bwx`jFosWuviDbv?Aw-B0QiUGSWS5>UdtF|2`G?C#F4tY%U87tlyT0K1Z?_7!-VtVV}9HG_V^w3`_S*Af1!Vk|6u>|{wMs; z`d{|{-ro}791s?e63{cCC16g#(tvdVPX)9GycBRe;8eh80lx zchI1q@j)|#76)w%I<8_WSJgn(2-RfOT-C#>XHOQG+FUJLzA>e=tn=6Db&u(`>GtT})Sc0Nt>@rupw-9f^Ypd) z;rjdaEA<=o&+7N%rVS2?Cr4A;f>)>hCd(vV)(J})8U_oe;Y9_ z;(G&U@HB)Nq72;(4;t1OHXEKZykt0T_{8vSBp>M?855ZiSrAzrIUuq%a#G}+$fc1_ zMjnWYj7o_rh^mNchCq2FuQbLQry3tLt}$*i9xxs?{@r-R_=E9g zj6;l1j3y>ECO4)gW?;;Sn8`7VV;+gw7PBYj<(RKyevVCw&5x~$of7+K?Dp6fV~@q2 ziMIhTOP!PYSn9Ua!>MnkevtZU>Q|{Z z(iCZrq+Lu8OhNAFC zOwCx7u{vW@#?Fi*8SiGC%ea#9OSh12mE8t)o6v1Rx3%3~?`Fw#&h*dJXU1oC&n(XD zmD!Zpmibubw#+@5FK52pJ)!%a?k{(LyZfizzsnM`09MfKsvghuIN0Oe9-sHP zk;CV>w|r&3cYbKTF+V*&Kffyfq5Mblx90E8 z|69JLz`a0KU@Vwc@Jzv>g0~AU75rT2Q5aR2SJ7LS~r5~1FDZO6iR%R$mE-NToTXwPR zhw{Mk=JHA950tMh-%`G}{EhN+<>m^viXem+&#EY?Xsl?dm|F2>#o3B4Dt_td)KlLx zrKhQ9P0t}cC-&Uc^VOawdVXB#Ua6@pt?XCXQaPq_N#%yh=PO^VJW+YJ@^a<(Rjewd z%2YM7YDU%4s&!S*R_(94Ud^gqtJT%Rs;5*hs$N&Ur~1w6i`DlNE8w^wbiwq6T*ZR~ZecWCbcz4!M1 zu}-M-sngUY)@9dK*7d8~U3aAJeBI}DKlX9%`dq0O>V4|N>l5p< z>nrQ~)wk5Isoz$Ap#H7;bM;@=|JtBv@NbB0=+;o%FuY+-!($CEG`!w$s^Mybxo<_^ zfqlpHUDS7H-;eu#+t=LBy`QFE&wk_j&F#0U-`0Nn`W@-_e!olo{@q{JKdFCy|K9zF z^&j8Az5i?dKj{DU0B%6wfTRHkcrak}fQ17#4A?i|XYJDXl= zI@xrs$ueldp!tK|8|*N+ZSehrA0GU~;N63d41Ry`)gkUf{D(vinKES2kaa_z9ddZc zheIw7`JtILdo}lLZfqXeJfpe2`L*VgL!*YK4!v*aLqi`Qx_#)2Lyry94~rkxeOSe? zM~595_WrO-!@e8#Ym0M>e~Z3lRLjhkWi6Xp+FM?0`Jm;~mhXpi!##&b56>CiFns** zCBruie{T58!_N%AH2mMKveuB+p{VsO&zsy)U%`Zk9HZI zIl5@{rqLgc{%G{&G0HLSWBkXYL&13em=k0EGd6B)zp)F)t{VH|*dNCwjLRBVGH(00 z_HjqXyNuV3j~kyhe$x1VPB2bbKjG5Eq=`inS5JInlGh~lr07X$lO|2tGwI5tA12+L z+;ei@DK1k2ri4w2nPQq!F=hUgNwSR>ddK&r>>m3aq5+6x@is5)=oP$?Xzj$ zO#5j%H@$v(^YpRPXH0)%`g_wqp8m!3oA>$Lr@1fYzKr_{?yJ79|9x%u{cQ#}BW=dY z8K2C!dcWWOjrTuy|5x|lm>D~>X66$!kIy_c^Rt=X%=~Fq_N>BLHM0iJ8a8YEY{%KY zvwO~7JNt>*PtSf~_Ki8FIVO$XzxeI$Pe0<^G z7yh*H<|56a$VCZ@suwjZ8oKEDMf(?h^`PH_xexYy@PP+kd(gZ%e{t2~=N9i<{KgWm zCF&*7OVXCie26`i^U$HC!qVPL2Q1yW^!&r&4<|mn^x<_6f4D4R+2CcPmrY;xz_QcJ zo0pGWK4bY4%U@Xj%JLtVn^$D5C|I#z#TP4nT5)rw!^(=4&#e4%Rq(2YRclslUiI9n zeXEYF_Ff&j+PFG>^_!Twceecn8>s0F|th;aB!gZ_Gy}s_+y6@KASZ97L`LUeGo_K7> zV?VApt>3%;qxJt>|L+a*4GkNHZWy&;+J>bY)@*ou!&4jDH@vjrjScTTUio;x$Dexq zxyScCeq^J=#y%Tgc_QM8O;7y1N!aAR$$yi6(}Yd4HZ9q-cGH$kyEYx(^yX&u=EBV@ zH^2X++moSB8lG%@a?z7-ZSmNWzvZDVUv4#QUAgu9rxKp3d}`TK=eN0StKZhNZOgVD z+YW7eYulM^SGWE2wD7do)7qyKp3Zr?^63>%AA0(iXL6sJ^vp}!RonY+fBjkAv(3*g zdG^E(pB<$;R_$27rOUaXV-2T)K1p&h0x7?0jSA2hX`Ym-O7k=MF#j z<@3hp=RN=OF4e9HyXNkCX4e1uqR+oB!bf|+_Q7fdwVXu;Qm7B3zaX-eBpx^e%f2I z_sPA_?|ozMnZ4icbKDoUuXJCZeKYp0+IMij*Zzt7x9mTCfIpCOAooD!f&K?t4@^EV z@4$uw`wpBuaQeV^2W}iNA9Ow#a4`H};=$~L0}jqQxa8p4gIf-sJ>+?)$Dv+_S`SS= zH1E)!LzfPHcj(s_17Ga^;=mW%4?7(8IjlJxb2#I0)8QqD4<0^w_^ZRe+W-4o+20;{ ziGQi>r4L@7`SL$s8TrbVSA|!{z53j%<|9Q%<{o+H$c5L2zxMoVw~po>Z9BU2_4L=L zzCQc)W3Qil{rVejZy4X0{KhlK*fIZOamN~tEjV`g%^q)VeDiN_1--TGtq@k3K&6`02Ojzcb{Wx$ivo&a3a7d*|m9?k6%%RG*l4V)KbtPh5J}@!j}$^WJTI zciOwl-aYW{@psR?`@>1@WcbOflY>uA`+Mr&xBmUoKL-8d(tEA%?SAj)_XFN9d4K); zufBidgQgGu`Jvl~;U5-$SbNI()bvw_PQ88V!l|!M`<{+CZ8}|ZdgSRDr?fN)Ir_;rpDI2L{51a4(ogUE^!ZP(UyQg|bg|{)>WfD% z{`T2~&lZ1n=yUG#*w2T5e(jRerQA#XE={_$^3tYDJ1-r-^!X+8<+#fOE>F3<>hiA3 z2QMGF{PyJ!E`N0S%H?k_-?(hK;&8?DO3)Skm8>guS4Le~f93dgcPp zt}eT}>1zAc*RFnW_43tUuX$dJyq0sV?poWmx!2ZRYrl3J5-v%(%H(97e%_WQ_pIxj zQuqRQxHHaXL<&Z#HYsmzHUF#_C5yyYC8T8W!XP@{l!c;DVCzy%%!e9KZ5QX2Ok*5dhNU0sL z9Mtavt{f@A@+06gz&3p60ZKpvpb_`ZBh8ZXa9+5s!u8j@yXD^^oJTn}sOK7GE%;u~ zK{p|$bS(mGYNYGImYc%gP!|H!M-DH4`+C`5%WCX?D??lU9{^b!#>5%41wi#dEceDg zgvp#9T5b`TFIQ%}zFP{P{RSZs<@SU3#$7=0MV@QYdCY-noT@AE`#DGc z5ySwx7kCtrt-1ono1jLW{ue-&!BrE`9F;eqkCt<>|1}tbHvQ2&#HljuH1I+5%`T;D zcG~hUDW&TU2)DcLxX-&_&H-d|G0)zXKnOwmqA(Vb-U(y{cK2!iByv4qIrN{qfe?tc z{1-v?4A;;V1TU`Fy#W6tBExlsCd`%p1rR<5?g0EQu9h$4BJK*i@!Nv{O6@>)4ByB6 zmm$RREe~MhIcNR=-gP%Pg5RD;JD$h7DyEhX_~!`_%}vYY0w~R9TFWiK*MLXy-5KdG zNXr1oV>y8|4(TnvM0`eu?>+GSXJ)W$wE=%W-g7_7rgXemuC;9I^-L*68Mtq`!cr~Y z$wIhV0&5*>1r-->yS7h-Q=NJE2hgVfzk&MkFP15b!ZS2RB#2;7p3a@) z%5nu!g?w$mJh495eZS-Nb0{x|^JCl5A1`noVoKK%$ZX&FXSvFIf_=z$C-}4H^DMs! zk0OqJ7l6H~BcEsa{+_@GxatbUut(V?;*jw8Uj^Ya%xQq^B;GUnzYA=6lXVsP~ShFJ7YOa!Pb(<2@_z{cf-izrBn+SGiy@rE4p&7_Qz5dFbmjT=Oi(@&n*;TzeojBdwD1Sl&aL z4B+{RTnas+x`9~ z+VlSa2>Vfw$*30r)n_Ks+oAln?;)HT(Jn<0>8ag!+x^?pN4OehMm(uM0DdP|b5C#p z`R)XN_WU$hXuZz`cLlzNbKpxXH}45jIRoDnUO;{R7eLqwz8)a^34N2yh5grHGnd4> zp-p$2gLMB8&TrqxrP=^WB(D{YrnjV_TCBp+Gl-3RFOZJ0)CJf&PRpLg7M(wy$>FL z6v{fvxey4?qAvS5S425W5c0TY8QuvUL$hE&{*y=xP|lCIw}o?)t;4+sz@xtfe(_B% z00(0=gID7DXwF@z!SA*B{bMdsD8=^_&P#X0({AZ z3Ke)q4L3yA9k3PetOmTtC3n=d9cw7b#HTq|;VjCOa})0IV3#6JE~a zUOb>T%Ksei{{h#)D}gK)XAT38z*#tf4UeSn>A)k^*$UK78?M9@c%ptGoS;ns{6X~B zYn%^cl0@L#74M9dRih4HVx9UF`Qj|?&|kR;ahyBEzoiGYG`4PwCJi)oj7NKm&u;@20F|Sn@K(acO z-%+l;Oo(SiUCU3LKk62R*v@Hu8`qm%x55>!H~1DutOF4?5XRzrG}`|PezU0Uq~u6a%gE9UD2LU%5lPeGp$fA}KT8+t1d_=l0VCtwXUnkL-a zhw_f%y9W77khc)JvIs68GLbW6p(pugEa%{j+#B^a@Y^jv@>47p%p+&CnRriLB)<2Y9kZ=-eBT_Q%{A06BjlbSn!v6@F_4sMuKiK917}*hQi8fcseSdM=)~ zg6%i<2$#$IqmKTZ8f#B9K+TRLJ&yVywY5QPhY|g0#QpwU3ZSD+m^abv2A+#U8qcY@ zUojp$&^~vmeRnI5+RYlc5I`8}$ZlT5e~R@=PdYQKnQHJ~ zTQLXJqF%Kd*Aui?&94M}gnO%?)11Mn6$_Ep6-FU9{Qbr3nXAa0w>b*R7O2uqL}u@Srzu* z8@NoF6L5DF^m;wUjD-sjf}qm?$Xp@QY=vEZJjM&b8LXW-zzINT#qWpl&MGcX>?e#< zDo3_Yyr<&S zg7w}?q(=bj048hNAE`6I_KXw9-Q0@rt$6Njq<^)pUF-Q*>L;_l7tX8ZV!#4G0f6vC zbtarG0@&U`b*8pcx)@LifGxM!woIf{2DOv$7su^`UsC|)G5Q1j4DS+H1!Eo|-3Lz- zBhqt}hkF6MC9VOKXEb21HC>LB{6ks*8v*n?jk|+@X@Fv3A=cN4nERZ08ejnCXBv*D zh-QV5hw^b6N++Ieb+@&}mePG2P}^+x#T4snZx#Z3X9fqV;}$ubaTng)YROcLqC55< zJ7jgtO~b*@ z1|QxJbCDjQ+_alasSp0r`+Yc>(|e#o3Vcy=2s#U|k9b5HpT%aw#(EWdlC`sg?C`M&%r{w4lZoXhnI;z_y)?t+(~62gQ?Ax=mXGK4k4v%*Wl5m^vy ztut{5eUq$N)+U=QdqZZCd&@)QQSx}TquO2VuMSab)!}NRI$oWw?yfFX_fq#)k5G?Q z&rm<0UanrPepLO0`WKBe>}P#7K^nD2r-{%cXp%LVngUI&W}IfaX0hf;%~P6ZG|y^w zXwcM@VCO>F?{*V(%*RaG0_Ln3b+I=liMufVKdteJiNg^#Ay@Xv0r&LAI&FN@$e!} zV1JW82Tv~r@ZbqN=!FOo52+n^;AARUn5?_3KsH1+QZ_-hME0iKLmng#lgFtY)Glft zb+B5a4pT>~0X)co2T$N3umcZ6!#9V&5Pl&1rSK!+ zZvqcrJoC_R40vFc>+qaeLFlj~$iAc0EPkUa+sE>vWugW1r1K+uBYZ7f7Csg}63z&x zg!kc<u$6eV0cnmNbu<*)#R~oK>`h2dD@^jp0KYw=p zQ_v^n1!gTgz)yTS+3lfnnWIpKnEP51$EWbX2T z( zKO_HGeo6j~TO0nIRN3lh^5})6N99FR2 zMLv|cL-8Elr;h@-!beaT;r$S+n5lSPaYB)#Fe!2sVTwl-QHlyhoFYXLsYq5lu6Rtb zK@pD#$1={IqV`;pyX7M}4gI9oE2PvSHA626*mGsSzT7v^8Vj*X6}9W1z30Y z_xpc-?|t6AQ_h?@bLLDtbKZ=1w0EAj&U+!UulJ&A@uq_dQ@ocV)4W%_tG(V{pU6Jm zE#C9q@W>>umumH9c!Rw`-p*c$=!YJ-r`QR7;~>oMhKjvKsVK(`cb+%^|9(0IW5r40 ze6d=bDy|b}dzXl7#7p97@f`j)vr+tBHaJ&0XE+x)zjZE0@A*62Mf13Gk8{8C2>zw= zfwKYq$L%sp4#YXux#&asd6&u&a*muPXUhHM67(runD1PT>x9n~1D$KcF3#^U&-w%A zEw_q7=O!`INnjncR@6FAVNCcV=A4g;xbrOZ|uRw498umzD75h8yiW$yZ zVuACGXm!342Ra{$`Oep3uJgWVbiNdA&L+|Be2*DzCvMz2+}R?QI6sIktWb||ei27u z)qjlCxCIoaW{Trw4{@a&Do%mE@p8F~xIvB*w?MCXE9R1az&iU*xu>{K)`)xMIB}00 zD;~r8_E9-eJObUzdO2UbCFhA}>U{!*(*{X!OggliIMU6pHDne6KRM{ zh)jvhh)j>nj7;@D@iuy2d4KaZd*6B=dGCAw^#1C7;eD+-RHwQGJ^nfBY;~SGUtOTi zRA;FR)e5x?|2;ZQ%~uQXFQhirt`@5$>TrwGU zSbI8x|0iYWY~2&1M=#wQV`)FVi!Ri|Fp><{yXxKbNQ|u|x?ET29G$Bxb*3JT|1*{9 zs4mk*x>(0_fBeH~h#sLX)m7>;U9B$Hd#Ee)o@%-7tKQVB)LZ&UwFt+HU#Z8a-{`&6 zReG$tTJNo{(c{#$x<*~6Yt?Uco%)@wSJ&&fxNWYek!4-sL_kcj=kxZgr2|U;R4Py4^a2(1JwiiAU#X{ zNgu2p)Q6})>qFH;daim{H>pSTJoTtP6kDUeZg{%ldHjitbW>(M#1jy-dBTm#f$G5$bilLcO6^svC8K zxij5{ zV_aK_;o(^6VRji2C(2CmTdW*^CyT^YayVw4!^G{-9Jl>NF#M`o2d<;#=hw?D-5wyNvU}pL~G*5k#H1Lh=RgAyYe=gu!!Giy#l4r{s(cdd%%=E8Y6-6tU$ z6QZuL{tk@wb#rTr6HZa0VD9|lge)p(DM&m#HKB$daK{KIt1jLgPk8Y;LlW+=#_0#n z8G`dW*UTwMOr46LagBot5>@1?YHTc6YoME25+e{~eFcdUiZ3C^ho{ad05xlx3KE%9 z=gdV&0mWsKtAbn=a|g|BY-}6^Y7?1t%?UAmPC`r~U_jO3e2>B88a%1#-aOGv5cg^^ zud%VEsWIVYRiwtW9`@wJ142=N2ba5&lv( zq@ZQ3o>yByF;vDuhGu#uB6H)-33vA)h^#AEQ?LdZtS!;Qz?Nxq=1v{dG`(?7VdId- zg2cEPa}YO(m|^oTPUxaUq;BLLSa~pSJ$Ui&HPlgIZBs(do1bu+K}ACEUYv*&6%eV} zD2o#F00Lsi&21!*x%EtHkD@yw*`h99yZex!wla#+YcJDaaYllKI+S~EL3~YN6V)Vh zSPY_CNE8eLkv^5E&%&m9lSS5ck|%~D6%q$U3n$7Zp)0JtBP-K|cnm5W(zyGO;zZA) zwNl0tElu^siJT%7pr9bpvu+}l3y#9t#zYSJro)#5UvVNAX!4jP1>it4$WG+e%`I3n zw;+)VrWGghiYCpRvsSg#Hx5noY%N?_oaj|FY1*7gGfd#1Aqelo;k}F2ioCj6bJphN z)g_#!+C=V1l5$8>?b@F7$iXAw^g~U!!=}zzOQH{E)viJPBHNtZhZG{2?}ju`JS9Qn zq1Z;0WIX5}kI?k0+D-+o#q}-vBa0J#i`F{y?gu{8yMNJIm)<)R ztyT0MP_$OldtlL8kKTid)<)>PW6|0k^xmmxZ3ew}D*_h`-xJT5z2_Fxp>F4rEHvSrC?Z)MRg@?mnJ7jp z9E}Dr9+JAvW?0x%RY-m4zl4Ao7AH#ls$HAiGftA07`^*i?evY$LH|d!SQ=u__F$q# z1?5b68L$~V@vS=t^f}w4mcm89yJ+cMs24zQ}ij$NHYM*y16Zd3Ae7P1znu1YZ?Ui+(z^Z z-P1IIH1zs~4NX;p3Xwwt%83`}0({%Z1cXpWtk8L&GBgAZ<3=}1fUyaQhcOX&VqBvh zG?f>GWe=Yh(0e5GaLbFrF<{f4K~$m#I+22chQjfbDb?Z_KaLAyIGqqP=Zq>CgRz^a zmnINMgQ`e)!{D2Ms9>64DsgMcn~S?{xVAuVabhn&H|D;ruDLXq=w4*MipQe=7)4xc zNc5_kGj$L~(t?e-$*g>oBFXag;j%wgydyNqop&B4!CNh&y@!Fsz(l?IwQW=(gZ9R4;J(_ z%^q_SwD3No5;3&$eYO)m0qC6kK8bR`Oe{)N;x&o57zeux8Zh|!+?rfO0-cxyHt$<> z2f8pgroiElW51$191fWZ2Zu}}m^h-Q6AU?K5DYnH5)3(J72S!xum-OE;d0nDyXa2G zgw27=gf$YJLvRNW9J>xAICdRGaO^sm@~i{cA(SUM4y8QFF_-crM-#y`z%h?t$k9wN zF-{BZ*;Ut(Y<0P1+3_}qFvkZy%46rU|AE!9NI4Q*n<0SBvz_X`8 zxym>R;7H>nfTI9ACct+z`xxIb#!2AE8Yh7thm>OszT=IP08TJY0yq({dj z**FP&wQ&;oDM-1O!FQ^062NK3NdTu8-H9uG{TXfT$V89UggbQVN`Dx~u{zkJxK}(q z9rH)$Yz%T{S+!)Xh}4eD5Q`3+v?tD;5xcWLZ;71SV``6aJz`#w8XSrAu))~K#lmnO zZx2->hiMMZsjbP_aoi5$^2gp3oKT;ySFPGkVSFCxUav@b`vqJHgA=d@{9FPt`K zt=m$+b~yR&?{Pei8y$CAGbYUd!E&l`Twa%$2W4QbSD{A90v*ZT{az=zI-$#>_N^u;%i?IJW266G^=9Hg85H^NF z5gx|zX$p(0EFK&0?1_7MZM;*PyvW9n4bp$=jQ8W^*Uq>g{SLx>1t!<`(@v1W{++;q zKGyAs;qaMoDRRW?*6l(IO1Byiyb8f+=qd|rxCedZ9@gCh`pdc2oe^?pL3eqg4bKj_ zdpZTs_!;~;*ng+9b}6qskq?_RiW|Wuibtp=~b3w?NE9 zNGsm^0t)&=oO{&@$S%>ua8u#xL>;w?X7<;J<#;bdSQj7*ptUA^%Yc6=_J2m>y8yWm z#BwnYH}o|Faxp?D!rzXVR>U>of~$5sM{hIF-L@H8fL*~>U~0pzb0<=_Ku_L^ya;O( zXsJYARHhQ_Le9deNj0Et&uwBV-U`$Hz(l2K1Lh8-OX1oUPqt2|v!aO+*LlNS-^Q08?($C1tw(|9+xTGTnGVL3XkkOeGdP`DIT|~{5vPZfA)azFoh&EY>FMM+xniD^ zCvJ6mIlY}euv}?&`ik4cxv=#ahTh<3k%X4Lzq5lg0Q(gK#pgJ&Ye#1%?3?Y3Hr0x@ zJ;d2XJna-ZL!DvHaA#L%gm~83&DmW%=ZtiUoMLB`GukO}N}Z?!Yd-9={NTi#a;L(n zbgGP&N{J2S)vQR2)LrF8P4GuxTtG~%4X1D%7MgCU7g@v?J>c*Qx? znd>w;^PFa<1^Xwh&U|NqSm!Ks+ML6jMNYf3*y(UOoh8oUPM5RPStibNmW$h+BXBm^ zO7xs%;%gCu{lF^cNarZ$Xy+IQ`{~fc9`Brhe(z&kLHDC`va{Mb#W~eE%{kp!1Fh^C z&Y8|x&e_;)J{J~JuR7;puj4i60$4d+C|-9iaxQi*aV~W(!>-a5*kSsObCq+oa}9Qw zu7g!eH8i`wbFQa7GxQK+#FyALdH^R@-t64s{K2^uwq$!bw~J4)m%rAz1E;3lg?+Vq zu%CUeb02o59&rBTJm~xxXQ@4mU8+Z&$FO7dg!82Hl=HOnjPoo`SbN@i0X6`%6ZeYq z7wmz*>b&N>j&s-Ebl!5_cHVK`b=G5t?tScUeTW^dkFdw}iSwzm5j$R=J72(Z;49~A z?3#b;{MFfnUA*sT&k1{Z|8RbAwmAQEesq3ves+Gr-k!kjo|GlowPx6N4XPr8IZoEdT3ILSaXV;(s26Q= zJoXqT$cb{2oDA#MMRJPV4||T&&T_77!ijfFmqVXUd#GFF3b|6Ql1E~X>}Xhq93zjF$I0X63FtZA5XC9js($ZO?wVkJhTRj@WaO8!n>kF)h|l)s0a=+W|Kd5ipmI8xp!Zhu)p#;?Bb5csqQDhUi>)u zwtPpvE7!~ScT$Wvbmh+&$ef?q2R#cW-x`TjSQcb#A>I zcN^UC?mq4WccMGVo$T)GPI32hr@GVJ>Fx~dpFiTxbZ5EyyR+RnZlimEd!T!ed$4?n^k!@fBEGtb={YYp^nT19lg0!SdoASe>kQ-@{GIAGja78(@XGg{@vZ|{=@yj-Qxb!{n7o&{n`Bm=R^wS z;6iX$DW#RCBC3bVP?sa~qL>Z9^iU)2w1O75Trz}jh$+EMMK2CJP_ zff}NAf%Vf+HB1dxyQ&dtH?_MOsftvw8l^_75>=|AIEON(%2kD`R8^`PmQs7FF={V0 z7N=8=Q#Gm5(!4@lseYrbQdg^M z)V1n5^;`8jb-lVl-Kc)AZc;a^Tht%at?D*)yGp3F>JD`$PT#~{u==CASKX)XR}ZK^ zsRz}c)k8Rw^AYu^dJGn0PpBu=Q|f8;jCxi*hm$&AP%o;N)XVA>^%u2Hy{cYQud6q3 ze&<{G|Ko4ORq7q}u3E3&Q}3$})Q4(=`Uod`exg2A8`Wp(bM=M#QhlYqR^O;^ao*=9 z^*8mM`d{HMDL;tu{S?V57)cu5!jpG9Xs?z*oPm5efbi*FCXLm z`bu4;tMwjwPd!HOrN`>M^*CLlYjvHj*KysT$KzDe33{TQq$lfr^%T9Io~ozm>3Rmv zDxIbGr@eIDs1MKwVh{abeTY63Czv+rdAeD*=vF;nFTi=GZTc|1NVn_7usG_}OZ4Ho zOD~1((Qbvya`X2p9eXqVx->)Cgf6@=a;^`sSJUyZx)sN}N^%MF@ z{gi%MKck=3&*|s&3;IRNWC2Xj^*5BxF^)!~?|Piddn4@EZi0otEwEv`)w>OL1PNFY+yPsHyI@st z4_g%6=iM((_8t%qiir0o??G{c_=opr?;-DD?-5uPJO+z`CtzFfl=rk)?LFf?>pdqf zhOO8O-iy$Nd@KGc?!_AK95F=fEG`lkiVI-T)+B!Cy(G>TS9&jduZYXVW#W48F9LUc zizi`)_A1*wyy3kG`?j~ecf5DK_1=516Z*jW(AyyHgU!&#up#;sRz#n9pTlzKOYbXD zC^E!p;uM^ayhfY}o3lg3XWrN1MDeKijrXmnfrZf~SR8%leeeApc1Qp4e(<)y?&wGF zC+}zP7cUtR5ho%eE>0-c5ib&n^oV3cG9y`$>`2c@P9zuS7Wazuj`WG-NBTzkMfyi} zhzy7fj0}qG7}+T@II?r3ATlJfOQbL|G%_qQJhCg!INmL?dt_v!C{i356&W2ViIhg7 zkuscoTpp>2R7R>I)sa0Sdq&2u*jlFdt`B>BhndJ5;+{_FfWZPi!6^E5m^yg zsiw@DJlU(6*VTGNYh-yxTWP!|Zr-&O_FZD%QTvY3yQH+P-uP?Sk92j`Z`0LOS$_?~ zM@uZ88vo7saAs{ZQnR?JxvR4yQq#Ghv!iuUW=&UH$AYHj<;z+lHF5K1(n_kb>YCfS znwKx0-`=`1v#zCcSyOX!YX?fh6ve#y<|ZJ*tE;n#Lrdc&maY;m7w9hc;#^!W&cKGs zQVUgUOJ8S8QD>>Gv!$;yrH_`xGUI94m&R*tfouH&S485rU`&0fMOhk)#7#j>*0r2< zNoj4RDMmc*jc3ZT#-|FKJ-)ef@!}>^+|2RavW_y9C8hD0+Gk!vx-?$L;fSw^Ot7VyV5s*dENg3TY0a9LjvQ1}WlsuGYf4pD z=}qRU^d@uRvL}Z?)MOOMP*%oVLN!@>$}Al*BQB+NHQp41XNtiyC4|SIh;bc(85M54 zV}Z@S*2YzN`vs*gjn~`it+%<=+iI@2DC!M0Q6oepQJ<$}zF1hXsf+9V7It=Zm`v&| z&2b-_g{rr_jhFP?Z{c!?c-Qj9?M=(Mj8ltfD{>E*t7_}Rq086nIS%z>K9D$BTvimVx_iq4+V4R2|@ z!s4y4_$n+*s|{(TRSjw;WYe3;gqd0~u%O8@til$jBJRx!YN@2OzKp@6XgS=H;{Llr z&q{GUZZmAKgv9+?ut?+ap0l>*I$GtdFxM+sizdd9zr`O~#c~-fZSk*6b87 zGG@25wRW{GZCmQiUeMKaL~B-KD$=B@vK8MD^BNh%1UFdeDzhwWsPYbA^qB{AYv9$< zya^n0=)_U&oz3l2Cf9p4qn0i$Db1?kaO-BKu{)bNRTB?+YAeE?lCUQl_QV1Y_44e= zt4S3ph(({D0;K^U923r~rYam`=qCCg^M;C?8lQz>Kogf$!yF4oq^Jc3>#<{q0kZ=M zd(3FU&fL_%!v3rpu3L6vq~UkA9dV3@h%Bv3m6C=Jj`0k1mKmNnD9a2h*3D8LAi*K* zhC6xjZ8EYpTu*gj-qnTcsV-G=^vof$FdlJUIq@_`=BCD|5Gl2$I@{$hH=bWv<5MDv@r_1n4$ljXbsW$uJm7IiZfewHzmf20 zia#{|af~;CUy%tD`JH8&4!iqyZ$=g(MOEH}ZPXCt%rGryLK=fD4W{;TCj_mQv6{#_ z+fFBR=XPDHRRFzZy&YpsdOOfs5>vA3nR4ZYM&2MpNN6Q{ynTZ(@Wlv@Hzf!IoF&W* zryP-G##DCq3J=7ZQKfQf(f=I+7T>>DUDZJoOUeZ zm`onf*qLQUwQvGMo;TaXn_-UKd7-h619E}^&%&ly;5C|{#uU@@LZhKc-nb2RR)ssj z1|wowW~}5yrY$w3dY+PKSxOwhP)q&+CVw-GT6e0K$xV%`A*|K5aWn-}4h&`uW{5VE za!l?^t)HhN@RySxqHag_8ulZdzbfL%Jp9!rT@6p8(9SKM8vkvlHYH_|COZvjva6t` z)GDZ{mHBB^e(c zoh@#iDQ>i+JhL^(&vL|;u+}eOWu!Gcp(?c~OUomzY0DnYx+Gf8=}V&3JPAS{-ry}@ z3bPiZN}0VNG~diz(2ayBQ;VeKYGG>mW9cbv$Xb{}WN40-du>5wqf%@Uq74~sJiVGZ zeUbsJv$NkiOJqr{Y&$H{X3N)R7eZ~p6cJKcnRQrtcA%>|dr^q~>_rQ@T3b8Xn>t$B zn!R?eTCbhUoZTLRQ|&0Hp`*;M4GkTo@iI$Stjz1+^jRHEOFEY>>*`#xu+{4@xH>|( z44Rmg(paV28mx@`B}BE?8Sn%vBP#**mN#Y&j&O^j-cS@Za)y%A_+=`O>a zu5R2xZ)!;|w?!%SvuW^_hT2~Ra{wzNf3Z@TwKP@Z*-N|O#pq=5R#jB~l2QW!N-UeIZQH4^g{f%pmIrl&(TmqX0Jn7#_urLzd5Y(8TZ0XjjJRJ1 z7HPbp=kl$2PR%yV^Gc+$>!RhnE?OQ4Jw}^qZ-o)V6)8?+tnmAJZv~GKS*uc!Mjoqd zwKtS|s~E%tH(1#!vkYsf_KsxqnMZbO`PF54Z5(sxVZpkn$#nbBl|X@H-ORL5mtSKh z9cEOiH4`T5DGh|EBAuP$6Qb*aK)DU{+jN^U$kt!cc= zO^sV2Qff^NWVLqZWo=q**h;hGS}1*KxH?NM6?T{lfz_r&fCfB{@#dRXJHN8bFksy& zj`a!;2ppS1qXN4OSISZ(KrF)&Rc6YN)y{n*Cy8{L%JK|`4C;YQo~9cL@KRrt@}NTs zJQ&SVIihn*6%8IcmtSFUnm&Qud7+Mh19F1i!NR6$z*}X4R;5~SUa0FZ$ycS-u3<%k zsemlgmoS`-NKuRtI#n=CHf&`aX|gokjCH4KAve|Ugs@f{MDEN(7e?l{aYSUksZNDX zs5!&$g5a_pP;(|)4$UqRZJgBk_)f^T?*wY!i9)f{bUMvBX<&JJ}lJ_u2g;dd~8pih(OT$(e3t_4mrG$2(E+pRSB40kF(X1kPTO&tnS z_}xXbng_pXrW~7Xjl*S`-;B8~8ghF(G1PFZgo=_AngYYyGc^l_PqpCTwJuwTSB9As z8yD>gz-95I7**oe2hEcKpoqkbfWi#FJ42XHm%%|@S#wTWM`p28H)fsGiTTb{6;lW1 zhx)Z=I|N)vON zjOONWs`>Q5dcQeifiKn>X3CDRoY1Tt-rVrS9sZuFxjTG@iBX>^G=oQ|qI!$aOg;sY zDqAVhkQ18Hr+_#y8e(%6y2$p{rAx`3VI~N0_31uU;8?#1Ic?$L$d;XDExRq%*Z3q8 z5!r;^UoV)Vkmdt9+P=&JQ>X0Xqj=qR7zNl>1UrM~lVR2p2vh`=6Ix49*dl`F!8cAw zbHmFD_J>v$1draRIl~`X;PRz6H+@)Xj+jBk5==vi%_g9o)*Ot?4kpcNu?2#PheTOH zk=LAIRwL{R@(Qm*QfYjdM{>%}!-B*{q7gy~Vrhx7sCW3ihw4O3MrY7Zm?aCsayqxF zK3c>Cc)j0`B4_^7$G|1Oa z4wX-c1i#S%fD#!&iWC04w6&0pLm$Clc10AVC{Lj*_lZM;;z~2CBY@_F)=BVYbozWX zxyDko#C%#%Yzoyhb7)pe@aVCO78m3@oF3FlGtAnGT|vEvS6GN)xy2&OI?>jBeqaR? zBAzl18-Ntx$HNY2s6}P3sRt zjQwW1>66ntZSu(gIl&B+y_kUdz`3cps6id9ZcW)T0?3?TN@!39^Fr(OMIu0B7P|&b zpY&;B2!`j1AwN$T!~TG2MpSs}7>-Y?Mmve*_+H_eB!~1)n@)z1&{EhCniEVc!_nQV zIwcyZd1eTndxx+;MPq8(8H%SRa#)mTm2BBRKXB0ufz#v!Gf(SH<9wg=IVgkVcbkZE zVDGfqCgR=N4zrjuc0DH-D~l_!=QpJis30j!;327V9{ zkeCxpHaR-uaG$OWngDQEww(pACnuN+1n7+RBvnofmI0@YfRPR*6>roiY5t&MGnV_1 zIl*kfig0v5JT%8cT0Sxlir%;T#3R6BXCKxdOhJNpsH8)=q|0wle@4R4Il;6foH!^##)^RZ z!32c?`=rlOIILHAs$#GQ^A+}HtO~FNlNN(5edc1Yg{LnZV))5(n2?_*GVIR@W;5aF zpt4MxC-qlY(ntel{24UEF^Ga`j`gOsz}{&So={di>tR1{XT*%DSxL-ntl(|7_+n;b zr6h(;PwF0G)yBdtX6(BF$NLzFx8Y{f6ye5RDrUA6;b)64yv^=h%{H+ye%vgyc+|plf`C#liOJZ?Lzp?v--=>dSJ{k)} zgxmCSn?7#KZ!8x}Vhy(Z4K}^C6O1+3dT6le8*KUplfK-xw{qLg%B}pD`|@sfEK6d> z9I>R_%4c~r&RqlAA8WH}$4|HZ+H`-kTRb#_WQ?t8~VM6P}gwlu~p@oTd^*)-J3OVj@j+}m^A^9S)+$onHBpo+l`jn=4wonk)Lf2<+eE(;|PTNehXh_ zn^~Fd7Gt)##w=ad#5@+W-F&%)H}>QuF=IV}x9vt^wpqt4eKDUtAHU5%X6cLB?%7(r z#H5a`3x)F<~#^NzHPk^&{j7=2$7LT!|LN{*lSX-)? z{R2d-*3w}uipu?Awbs&OjFr%BTYTof8Sq>Fng4FUZ~0Yc`Cn)2!`N)0P1$<4=H)SC z+JbOf4rBTPzb%I`R)OE<8@K6=F$=nN%ZIp4ZwxOGZqvst{c)S#7-XQ$Sw1w_^w#_# z)?oS2VAD6)^bIC`xt&&(+v!xfo!*rD)0!#^Z>%g(PX=Fkw83=OFree^+wbl%Gfek) z>+Z`cy2V#^^V{w}z*n6fUtXW?FSEl#0N-u6DeDg3ke(h!t!eaPxtH$mMlUQ;)8o6* z7pvx@Nild94hij6>M0L9d~Jc6R~_Xn!Z=38SWcnIMLCW>TIo z0$P^_F|?>ndBSO|MS-E9%CL&Z$uc{wF0=h(nVGgmOU?8j@0uP*wsv)n>RdWMg8#ws zd)W&1XDnL?lR*mUF~4(pmvOWmVPKZFt)zgArT7nD2fJF^7A#!GKpkxcQ3hjP+SW0@ z2cc#M;U-6>K~ByL#%^4Mp5X{T!wpd&VpPK&STkpk8;3^@GqN>-6A4=+58CO zrZ-_mJmE#W!3u}L2>`~pY`&p+85No_l7n%=VQHpFL$h%)W`bkf1ZT06)8eNjCI`5v zW?GCb5D;qk2H8!8=6}>Iu|p|>>!%SVQD6( zq1m{IW`-l08P3qmPD3*TGR^E_K6fl%+{JmYgYqDU&4ZjK596Xd7>@E_IFkoEO&$!$ zd63KSrP9ohO6-VSVi#s5X5@~Rn1v(WmM@i-FO`-rm6k7+Jn}&CWcXQH9?L+Da=r_* zSw4(=X%@+bb!Rh)){`@@sdLb*4519xXcuqe%l7inTPGn*7nx<23an9ySPnJMtS64)Cvm&3((63q(E|(GRP2*VCSpi%ey*FHJkPWINP7p)T{Qkt|lAF(^N$AG!_2{s_>SyE(IPN zZTluO3kI0Y+bBf<&Z`x?O?cEO#b6C-`Z6@W9<7U)EL#PPjIh*bPohz)O#<p$%N82f@+B=D*%md+E$mHwyzI{j zkcPZmkJQ{4ZMm7$N6YIfGv<@fx6JEYX=1P}S=QCow7}9`4s8+pTMUWiMjI3@x1CnG zS!Te`(_>&Xj4drK%OJU2+yntMMJcWD7Poc4ir8D)+T7XE5`py?QAA@q3>oZNyJ;~* zdnwEWtQeJ);eYD1w#OU4l90PJMf@pJzi$IFzjfLZPLhbmaDN z)%IjXEkTZIYUc_;EDHeMbrxqGrKS?%ZI+m2B{ep#24STcY!wp9uMDA8eS*+KyVFDQ zJ`FL83EJI&59IdwK3p+a(HO=ch0(@OO&(dO+HDz{LQ)abTWu>***lC;B+TOV$ z1#DG{WmW}PX4i;iR;5*DRiI^7CsAf~4P{ohP-b-tWo1=7Q3I)5vw5L<>mE?rJs=hc z1Bz4`j^(=KfSjO0!|`0JCO%+gIG*dbdq7%twH#|{kA^*U;dETp;n)iHq7)M{&@RyiNF+L)--#zgC?$n=J* z)fzvck)kWLp;(eOXYqbHHU8#Rn0#TGW87S`SuL$F`RZtC?LuV8&5`}O2bePT3I}s3 zZKqX1om0r|cUlx)9SSeAZv4$6fG4*PPi`Nc++|iq7(Y`M3MlCwP~APCEF8djb`MCy z!MS&bFgSW2(b~1Ft+}Zk*0s&67^Y`hkig;Rbi-jT-a=&4p{R0yoPgSdGmG{?*hHMo z?1&R^E|7zBhwj4j9-I~A;Pj!l@q8C2(>OSf=3_iR#Vx*$*o;$59h_lWfoGL770>B7 zW7NS3qu1k!o9ysR;FdB6XK22T=bN}G%E9TDWAGe{Tc8}AGr1VgPTcn7;9N%>x{lKn z-@@~Kw@^6lFt;4fO7}oK4|Y$)bG3Uep69!l;dzC751#kB_v49MH}QN_X`GxKQN8f& zqd*(Zog0N`iE74kzM7BcLUkCPi`CJ19;;5k6Q{l5d5Ss}&(qcEc;dV_JkM4a;CZRK z6wk}m<#=AHuEg^ybq$`^sT=XUNr48O;dURM52(lRd;)j>IyjpR<;Cf2Z{qomdI!(< z)kk=KqQ1iO8}(N_zgOU)`iFLfLnp1_*+XaI*;5a}6X&PlIb83G=Wcp8JaK{=o}+XO z&pq@Wc#hHFB+g75hi9#Bz;lA$7tj6lG(2bM{qdZm=iqsOJ^;^ybPJyI_2GCPp;zL0 zH17R!aON569cP}c#`9Et7M|zp3-P>ILlSU$*-d!fs_(@U=ab>u)YEb1m4kDfzQFy6 zUqyBmF3u-fjB|9Q@HkZBR=A&q#A!@YICK&do|%!MLNXkk%S-Z?DW>3rEVsU4@=TH6 zysE2R3~g^()`2r*6-g%K)WyriSz0N-19v3kmpEaohsape+SMVd!q3G`U5muHwCAG5 zi^Rmmp(n8Wv=Hqs<8^`2LmoXu?;`c^y^j>F`^&lwug-bxc|i04M2@J9$o{A~oLxqA zX`J&lg1?Y7p>cW{oioS z+|G$lAwSJy2T=Xredum){-8X*Ycu*B-~EG#P^o~QPDk7R2RAyWKFK%PU5s;n1;g(Lb1;$%i-wIV2y4-%WnY{_rE;Qp6NkN~89q=m7y>CMX<% z{772qGhi#Om~bjf_%i}j(I=C_QDZ+MhrtF%iYxHva~Q=?dg9R-N*TZytVC-FKV)Mg z)tId{Btjh;{)praaGTns&=-@NICdjHQO_I~!i8|)!uLy{;ZKrps!bvVe}>T0q)V<& zK4W;!DK=8MAoH2YEdVhkAep2xGTwbAAB7sZOcg5~Zn+$t@g`it}rKL=Y6FEog_PJuytK7|b{bj!!YK%8*-xX8dMmrsbHIO+09z&xd&68&-F<3g@UlK!bBIe6T`-*-=?0}Oo|AMsZ z^g2<9^CyRaN}NF{cEKr>BTy=wL@5ez7Uf8ks>mw>-J?95g^yDyM}tQtxCgg4?)#03 zez@1S4Dc~8CI+BSFBd!GRLTlq+r!%fA$xgyp@e&Td*fN-)rj43GG(3E4QErv@f`1s z7b9^p!F*p!(a9>rc~(jo<^-VlhrO z91D4p*$>nB)pKah8nGiz{6~+45r#&;DEir4ys4iWfVQYHg62XZNtQ_#qmaTw(PM9d zI}^PU^-Nz;4^AQ6OHr#V1eQ$TIFltk)iS!ff>wbD+Y-4i^5HN#1nsMPJ0m~+sHKkp zoJPAN9E4LR|B2kU-3O%Fe$aMN0v;zE;&Fs~G4#VXl_jc;j%rgCy z<_%03i;3&h_uWE)m}_sr==}j)G)tkp{2z_spoMx8{AfNE#PN7e{7KPIhO8!g^J6|w!Xg@i>K}A}gknvQ%VIhgyAkn|#b^rv2SznmGFO@^k z=E9(%KFe?fkth+(H2MXAsBzB4Gyp#|f*=pVr#+TW{J2!?WDjwF*lw*JZbI zGN#QTxwSF`3fJ?RiX^^}zXUo0B`d_-X9I8%PSYx==b{zoFyuxvkzDvptD)3aYEW|M6;9)_kq&AE)g*I{dC%=ZB#%Wps}*5M&qVGU zfyLB!Zt{oZqsSqj!jNNHd$Y1?B>xzq0g^PBB*efWFX)_z(;*p>SmO=Ds!w3mSA{hd zt>yaQh5_hYaL2${tii^4 z)}Z}DYtVkY2F=Bt2?v2Yv=Yt6-3f=lP3zHYUXN;Ck4AVsni*P;W`@?IxoPXsHm?mF zq*ZAy?ps&{F1LH_;O}B@G29(q2i&w;&BYxIO8`l0)`7fc&0;QRF_*KL%RBfhQ8Ayj z3dO8d=*?P%-C3)!D{B?9S*wuAT7_cPDhy?ETwlK=N1_(lF$+hC#80L9vFRFW2{IuJ1gq@6lY}Ib7ePxxRC_zWZ~1 z@5nlb!Cc=vvd&>J*LQ!e?>w&W7}s|m*LRHTJHqvy!Sy|w>pPeA52Lxhb6Nk;pX+-7 z>mLT;UX3;}gzLLM*Y{3b-#c@C@5J@JGuL;2uJ0kNbt?0Y^Ntf`xMSl4JW2Od=B>sp z8&TFkm9Yk@jPB4tIBB4E!A%-xA(r$|WvquPWw{y7ax)T# zSYuVl8jsyr5p)@&~+meZus1ql%$uA3~sv zR6^=MzvFi|$p3F%7no^=!m~!ad6mKA6J_bo1X9`ahz7dN$Y#eyq}zge!R!g;*u(pnEQ!c0+L~~~3 zVfFwv!9}Z;eB@^I*)-zw2Q>e}qwbF=>jH|=HsSX8}yM_ne<40V`h=u2Yv(Df7{3iuP?bZqE+w?86aPTg|CmjJ=6SaDb3DkEQ$kzZ#^XHxJ|PZo-r4YaM*7gqc3@Q{QG? zc$v0ksOv%D+xnPZp*yZ^K+t##ZE8AIV*v&@LLbr~h42~f znfOpR^$eRKo0y3i>EyObEr3Rxv^K>)qS?r3z+c80p;90fwHg|eF{{9g&M&k7<{B_G zm?!bcx_(uzKqq!?(c6#?5#uht-x8H*Z!~It4;RfH3^$p(fZv4h@2M8KM$kqL_kvuh zjOlXZWAMwq?Rwc-Qhb#EJ8wJgZY4MW0{d3w_!qETO7SGA#Or!J_XbJnw*A-*C3;8xFg^hY<3xcoG<%f_2~CxN+`d zVE+W)9>dvdNkanvJ~#9tcH6J*bp|b4PgUp2=^0Xa6k0|+^xJ& zE*3lBF6j<25LSgN;a&yX!U?b~ycB6Klb4HKao_WuaQ|8U8PBKWt4Q@4Yz`;jZsoUy zru&tF?LGM(p6|hq*;~#@z-{fV+lIgnPBS8qc#~mpF^<5@*3K z@jB5Tcltgo2H-Z|N5ml9==+@52Y2|sEXLyg-Y>*t_Zy`|8SdECVhHZp^~8R#TFikP z_veZn+><*R?h;ig`r}U9W-*rTvlUs^f^msjB6{G?+M~oUSTG)o=W*&dq3P~ggrBHR zM94|%B)He8H3&ajoh^pr?%Hz@egSM6U3HY~j94T?RK87K?X8HtP2Gl&dtd|E12@?I8P<#st49F&sCpD3 zPpN0%eonmrH}0&(^Hue#7{#`c!*F-)8^~q7T8~uksrLYhTWj(B2zHW_U?;f|kYB5> z;r>Q_BRsmdR*c5YwVMF*H}yBMH*T=~4!M&R<=(i(_8&sw9@{N|#9g*X^^^Jun15D3 z!x{wDDn^6Z3Kk>yh`n*Itp|${ToH+o9vYGctIJ$bg}ZI@V3*Qc_eThBwZ(Ig9)xh* zZ438cJs57>Zwot>{1V-TaNLUvm=$^jVpqaObO+pxdnCe-(no>vWArfyKUN=$a*@?&6>iHt9`GmX zlY!w>eL5i5=(FKIPoD?(1^NQOTnKB@8L%e37|%=eB?!Ttxq!J`Uj_Ho`Z~C;*Eixx zwx;{zuH0JyiQ94!d%M0J^yAK4xbK7w>i+tE4U1n`qC(!#S3WEzvJSACb$~-z2Uy5D zz^SYQ+?jQN6Ilni3+n)5tOKlM9pG%%0mfMeIG%NYMXUp?V;x{E>i{RR4sdtY0mfJd zxEt=a9SU74*%xKAwzr%$fu*bo%wtX9IMxK#uqH5%HG#cC_C@)u2`ptz;7Ha47PBTW zn{~d^Sra&tHG%uG4sc)A{!L-+-xSvV&1CIgIcxu*>xH(roV9=Tto|MFP- z*N3%#d$RVg4{QJSWbI!bYyYOR_HP<%|E9C{ZyIa-%30S}&RV`a*6HQ3?k*3v-VCp->YE#UX=BF zQP%H8S-;np^?Mbp-;1(-Z%5Yejb!~^IqUbzS-)3~n{Q!>&04;4+GBLg_bWTb8-sPmezc^ccRZNZuf8Dd?#j@7D zS)PeeY@S>qkA>@Ed5wHgzKfCNO57m-E^arc?;JG;W5*Qtd;ETJf5AQN2(e#`3+&3o z?dmbymhOjBx+S1^rkEfu#2nyZ@rZa7_Z2^mJB6Rbi2niZoZTQk!pQ%r*eE^|pNlWV zm*Ok&wfIJSi*bIoGskIk4sZ^14ss55u7O4Wv(8Hxu{Ss$IbS$mW7Ph^`30l%L^)Zu z$h+LtG+LpB!0rwbPxm?Rf!&L-*tIZsHP3)-&W0S~SZGMaYSc9DKE}Prm!dx3^wz`D zOrl-o!P+hl_IWW;4C}iQ*aIns&0Que?lNIrr?E#+EV7&r#SYE}F~Y%^;e0Ieoliue z^BJxH{zB~LKxc(n#bC@ShRO=mV{hlZp+)) z)B~Ka@g@vd^8iaBu;j`r(qsYCx4<+4mdNA5@4);D1cc&M?CoyU-$FodCF-n7D-tgq7-mdsf#&2KzrrW zMhlt)scFRf0KDfRtOdV|@VgklOYyrFzw7Y3Ir%BtBmTKC@Jz~a$7_cC9{C@R()7lf z^)9R%a%sK8YpYGz6i$P~FW2qk$Fu%{SBOacX$pw6jR>(TMYu>m z*zJ@2CrbUR8ij6&{w*)-8d!^fl0m;fiEIg(uI(e)YJJYDS+1o3M=CYdw(*5Z8k9K& z_rK+WbYRDTw4#ueO|17NX(Nky+JV{3A&;|G@H^D-mk43IHo#Dwvkn(2H)HRC!b#ty zu^u<|lubOMnl@!22xw97OqyJhUBdq(w8*5B=A8e# z^7|Sv;QJvgKcL2x#ymeGnwTzA$FOY!-HIhj@sfY5hLPraHqyuFN@D6Dr;j<-VoTMYdZWXzI{R8RP z4B2}J`C}g$5@vLx#Pc69+K^^l^KQam;sE+R(qEAcB6?HS4)%l`ZNR^(DE0@Di!aKD zesLXK18Db?N+&$FcH_DQ9}OAuLV9DTVNrW{1_NEiEOHM_&38(C4`O-*hvz2-xP3ff@sztz=f7z{v z`(wbH-5=sG_XNasvPdMH3{6-#2NSZb=eBr2a{KF_tx~dvd8<$>Y2EM{8hs6Gx5)n; zFUr0pU8aeL{wMhlaOmHZct1`0JB%$oW}CkMWEW$b2>w;zuhC-KetO-QH0iy!wIrdFJn4V=5=Ay7xrXCp<6zjNr_-7P) zuZ^MxefT$+yJB|BbbNs@kJD5CY4)SON`7X0SDH!Em?5c0WfyuiqKH1|dx@%T^)Tf|&CHBle2Buutj!>^k{jVJl89!NbQtsvxlqN0IkNq#- zmQ(+BYQH@)rQ7YF{!^^~cJ|u@B8cCzQd@}-IPU(j-IjkP;nh}|~4ORBNm|Rc;Aa7piJS`&*k=OoOsQgfh`$aw5B4Q zv=YONnq9O4p+3d(kKp;st$|5S$(=1hc$H&={-YNph30whn4Fl)G~eg-17_iaK~HA# z5>PaP`fBvF_(vE0rwi^)=*O{F4`~6t;K?(|574i|iXQJ==q(1|n~C0%#t=vp>M$34 zqFHMcsW6L1s`uf4pXHZIN^|%!nsXy&3wqh7Fzeose2ep-){3zVg(Ga3g#%_Ca`R^o zLsPSAVt{Qk5$*#_lMO%SZo>BHm%9i1&u%li&~K z_yI{gc(RelXzCG|R`3J%f&|N{jQkicVL`t_ACe<}zsVyW+lC>Q{#S#KDL(@+7p%*s zVIQrP0vR(d<~+|A3_Lx7kJ}~a4f-0J2i0AGA(Vn;-1w;m{EwN5_&)#bLjZfw_nJ6_ zd;ux=0yI#s$vr#X>HG+&HLWov0fYlP>^8CfGn76=C!~NV94zMIxf@f(+D3BWOZ+etlaIM!-}sjbWc)iw zH}%~EEgV+WLKHP4U2Y-4a)R%+?8wPz5 z(!GNpuSA;nnP!^Fet=NYX3;+n zNkfp&IPXUrp^`n$wuE#_1lkU0pw;jQ?!S>6_bKlaWyq0c$Zv{W5C?4me$Q|_q#T~7 zln8mnv@N)4{Y*M6^kq~Eq^A`;myk+Nw3$%2sjnbi8u83%X6a462-!!%Mh`Uop87(B z5KWtrGt~zyaPa&Jb5H2~QFcoE0dh679<(}00d&ut^Je-V6(H7=oiaXD@=q|oEk+vr z-xV=LAL&RoVCGHOVaExan6o10UCxQ;8{|SP>heKbia_59z1wi{3@`KY@uz>7Db38y;blRHpGN;2TyD zfHC&qA=IG7w-O{CQyu;Y-VL^tAWxRpZ`0_)P79z+K0h;Ft{+ZIxzNYTsPSy?3GPvw z<4^J)OY1vU;>$`c+#mTgBPrA?WRf*obYvB>sn^u3Z>svBhwoDz4d%uorHkU8p3 zb+61-_pAG5U)W5&DEq0G)f;j*^_F^DmckzDBUuLPr%iGXvTTy$$+AiAqqb-%Cy)h` zoI$ora;DDK*>V;vmHNv4^$vQVJODOIJIaG$i!?$WLRLt!88$~{vITZVm2v@Wi0WmV zZqVc9VS0+5D%+mG_c>Y~Nj5(6XtM5+$LLe^ zDe`z&_nak9fGy7zay41*$TMKMbAvpGEO+F2WV0jBCp#T^A=&B3%jv{8c?DUE$ls8i zh`fp{MC8?E8zQg4{{M;cTCxO@zx7V_PL;p&&hXBV*OT>!yaARSzn3?XO^3Y6gEfb| znd~^^Eo8wV|3Fq7@;0*9khen;Uq?T(GvuNGU)bxx2GYfBl}@DD4c|fkkGXS!ud2H8 z|2~g_iwF3t+fut=U8hkwbnY;Qnm2^t?#+_+?x;-XUb>(xu3hU?%L0@&pB(a zyN5Ded=ACo*s*MxJV@r&?kwtWPw|zC#%pMu1}3S!kr-4>?dp7TGuCg%aipf zb?fx0ai_S`1)rf$ojcR57yMcGIh8+KpBndhcb?$$^{I0gxW5;Cp}R%BZ_+1p-_obT z-KtMDHksw_Hg}h-I?Xb(63fg=_mDn5_L=3*>@$bTKJzKH{4;%qxu<2TS%aJG0fSb$`;Q28&G(i_Orh^eSDgH`E*I>apHTV7-~bdb7YY>&+ssL)z_P*>K8I zLhsT7rTcmN3$w^uagvsvo}+)-b|%tw^=Vsm}>>={S>VC zQ?TAorDm~NZ*Qhz>YW^(BKrxmiX0JsCj5*rr-f(8Msa=kSz*o!&lYUflI7t!iuza> zej)sVtH5$Hz;bdZmXpJ=oGiz3azuDxc%e$_Jy)vtT&do3rDjW6i7jOiULIbqHm?Y; z6yEGAYxMqKEsWV!7K#ddRlS=u{m6H>X(|mt>IST&2F!lcCik7#k$0g6f?5~d$|Bhxd0ovO044o z?BbHx#T8%?mohpiZ*9>**+7|>Ybtqb@>XHYwyncybSuX7R&NpZ8}uG*(7T~!3$a0O!3Mn-8}t_cUjK)xYnJFO z{*U~}1e-nj9H`qu?9mIbNAJWQy%T%%gZ=mY_hqwgR_SAcAZTy3=Tt5uU^~11SpB{Wu5p}z;U!RBl z`h4uy7hu1BB=+k|gWm_gS6#DWufd9a3|8!)2+fLpOjxc6yhnwVVWnH4IJ~uj>%uz0 z_2E8(&BlFX*sRFBE3kAQgQffQa7MUXmhPVnk8%59^?oE)@8ub*_Y=aCTwkb<=$cXd zJ}7<(6n}{*{%3_bD^z^d@N=U0eNgPGAP!i}M9p29DMUlMF||De!p=u^=BCD8po=zaow`U0qbAJo4DtNH@gfD+b# z6l*|n__gqBYRzoxOIQPzXbreY7_+l42yYhk@59zz^>V z?^a1$2?ADvK30Mftpwi}-qrwL@rZvQII_$Ck=BDg)`Jx5K?&=@64rx0EcH|26IvCP zuqu?WD)`|~wI1}b9+a>iEM`3jSr1aI2gQnUyj5kM3!m3q<*fNrtO|Xs2PxKrqr+Fj zSKUfhg~hB2eW9%i2V&)aG^;`(t3ny8LJBMYBCPxiSRsnSKZSo%FYku$sz;Im_ki-zT3?G0sT2Y3} z0^U3U*5C=yf+xTlJONq~`y}>}Rl4~DbmI@ufdsHhS6%ZZSc5M?OJZ?iu`uRQ(2Yky zk5qzi2Q7({5+?~`z6af`xO4D5ScC6DPvT73G9IeccfHy-KZF+i z5V{keOMFiDjpmK825*E<5xsgiJ_#-OBurtQZowy^Cvkq_eDz|U33Cz`B`(tVZRH-0 zm%n#9$KtKAU17uMjt(2_9kg*kXH zw6ONiNqjT$O}7pohB=8_6Sum3-~;9)ZcE&z9{)M<&uZEH8CvjX=*6F*1^!@8;`@p3 z>$>v28jNG;!?$67d>h8%+i(QF4g2ETkjA%RdBUQm?vHQ7RD2tT;oC3@--bo_HjKl! zVK%-EGx2SxO<2^_PvYCKKfVpq@NF1|Z^LMO8_Mx*sKB?O5#NR?d>a%wRk2f#!?$4s zz6}%bZD_@}VIomZ+VE|t!nZ+@QD++>%)_w5pHh{++GSFhC|@_#^b|q zIGkS!ABI`@FjOahk$l#j056!575x^qWBv?n@P#S-873*3Nw>H|S8{ym%aT|tnwaEQ znEQ zTdCa72G@f`LLZZ?nWRY4IwVS&(L_2W71>paWc4EXVAR6y(v0MIH`yAON%V*-F|bJg z))`So{ynhdz~5@qA`l=MW!4qV!pb1MH9I=fC`H;Ov!^q;ur%PgRwTpDyK#u!ERviM-$8k%E5cIU#vY9z>VcWJ2tQ~RRpyOI^1Vyn8GYnH zeE;8(#xRS^UVZlWf9F3I5&xZv|HC@@{r)YkV^4;o`)0PH_J;5mQwQmlbN zEc&WFduIP>mJ0TS+a1crOlVhKS%p~%>09monx=#rYDd~pJs)~nvwxz4cV=|SMj;~| zpD3m3+xu(wBo_6@=wuY}XPavLT0NNc@sHG&B7M-J#jKRAmg+@XWZru9;;S9go(%XC zR(hNP$h@D7*hV4yHO69Zsos!mksOr8?Ad>{@@RhD3@pom$ezkZ&nKeKqFyRtHoWgg zdR2`jvbL6qLkX5K(d$ZkC=^xUnom6?FF*b60qYdYzl^EwH z>Gur%mVcmTZk%RqoEt@%QIgt?i)z{k7>D&PG0 zO+8rzQf!LsITU2$w|Td9Uy<;r6VI~BTQt|)^}e$6Y1QK{i+?jN(oXBBk_KKjDS85J zz8aKca}Zr`TAa968uek81kuxFoXE5K1*I}83GrdZx5R%o4z;EyD*EGD5NP%7kM+y0 z&ECEI_s`})^TuB^Tc&-dmMqVB!ZW#p{qrS$e<^5^0q7`64d+X0xXs-e{61Xh6^4t# zRo*J;tmgWE@!xYHQc52wrG%70fh!FPgF;2bC<+=~h{UoKiDeRrWl9pu4p$I#2AyuG zPW`jjuz$^NjnQ9 z?L1#SULcugD4FNqyRzUyNj@u)e3l~lEJyNLf#kCyxGuQP`N%(qNd9@d+WD8@ZdVoD zBU$KBWT7?4LWdy>9gZw?1hP;MS?CC4p(bjQe;BgRB(l&Hvd}_gp+k{{79tBRKo(jk zS!lE5ej~#Pk_Wbit%4^?E?RwzKNp3p73wgbr)izZTTug@UAx)mdeVWOA8iF)=Cii9t_hu&d zW(d;cncRaiq{%apCJ#ZHJd-&c!ko@zPNy-a)0oXhq{*Y1!7SY~h>Gq^8vH1`Ip zB#KbtA@R(f(q>vL4@sqmjYq%hS; zVG>ATqIkm9NMUM_!bFjTJCMKZgZ!lh`AdN0WgjFjQDoWaNLywgTPa1VQkoYrxD=^M z6fw9OsmchXDz!*eMj%zGMXFMb^rRE%Nfa5l6zNF`(vwo8CnZQvs)-akj7YIlk)%`; zBQ{Np*g9gwril?-hlHgX35$np#Y3LrAw$^*8A>TKlu~3UrN~W6k(hW$OeP~SsX|ub zAuDM{RuUp5@sNyoM0;&RKBD(WX9r0>l6W_{NSdtqB7useXS{_dy79S2Pt7LFRxR@u zG|RwDoWH@y3GE*n6VdsbJe!S4(P+IK5b>stbqe^ z|E51>*Js?b#T(D8%JOs9N~%9Qjc%my##h)AWA^Tm2QgfP-Lt*=r~SwO2Y=0bIEICq zEA!73SEi4}5qMPpW%a0eOzT}!EKgy!=$GwBGIASuv4MAgkPiQs-iiE?*t0s={w`N$ z@7%Bc?`7_nLD_F^41?qH^Y;uk?lStQyxq}$)$E;!-j?Y7us5Nfj74wFz;jLdHSn>2 z=-f@&P}s?L=Vea5yWC;}X?2KBy_3v5X(!*6=AC?(AYM_DxJ0RbC*MV9-r1RH1xVrZ zPrD0|zsG0Zc|$W+7-ol74L4SeorMoi7TJAOV^vXuRYeW_SvB?)0hSaAEGdRzNs+{o zq99{EVLV(lmJwBOa@ANvgxEo(u!AVT4x$h{h%W3P(pW&Gv4H5p0%9%}5Oc7A=)wYG zE*217SU}9h0wV2OU$k-B>_WU;$By1w;iF z5c9BrNMiv}hy_F%3y3Z(AktVsbYTI}jRnL!EFc0bAj+_SD1&D$f?KVHPpySRt%Wx& zfh(=a7jJ8R)P%i%Q} zc8SF|48F4(zOx!Dg8(aoVep{USQ!k%vLL{Ypa!nA8ruOM+kpV9fnit;1Xv9W!)l-! ztAPae0>iKo2(S*Q#yTLtIv|O4z%Z->0;~Xr<;CKwM%zCOezzLk{xJ0U!_eVZqr*?+ z>FulGgR9ZThiKzd@Wj>V;RCepRcPJANZ+p4-FGQC=4!O-h49VQXxHbWS5Ko?uSBCh z7hU>Xbm?hy>2uMgr_rKUphZukMXyAQ-i;Q0F8cGiXwK)NIj=)=oWr zmRF)J??O*riJm--p1cAL`CK&QmFULjq8sl*D?S&kcp80p1^VzxG~wN7!n@FcccBBX zL?e|=?-`!}x=c4^CMEjja`(26NdoFtKG#c*;G~V54yyv3vu0+e7M$27+etRzZ z?Kx<+(`dFM3$!$v?Migo-RQKt&}Pp?o865by9+(`Tr}8eG}v>|UC%{#okn-vjqW;) z?z$VTbvOFzZZy?%(Nw3=R2QPDPNS*r?x&;vSDofL0loCT?1*NtBZ^M&>|p0Jo1IT9 zJD*7NJeECA3%i|8b~sTCy?xl>jAw__%noNVJDibdm~}R%B4X)m&SaaLf`)k-8s=tp zIMdnT%wmVL4?CRc>~I>~N;Cx0%Y` zW-@!5QS5C-qZuC0-ew$ooAK;z#<91V%&w-LUCm^6FO%85RHGkGvxlihKU{-;IL&^h zn*Gdl_A}$z&x~h3Gd`mewv#%?qYa+R-lm%UOf`F$YIMBQ*vV9D9lPFkFS6Lvx$9;h zFD}*mDl8iKSjAR~bY7c~6`4%^`Gr>I=&M_+j3iIi$rYO+92mY`8oI7N8VNX>WPwagkB z#i%vw=a989WED)X3hpizt)E#7CwLQN^BBdMjbh7MRM{q0yC&AT2G+I)R<#CJwI)`z z2G+AC*0Tmyuj#B?U94N(tXo~ITk~1Bnpm-#SgV>?pXTR9jGfNCe{qaVO1p7Cf1BD){G|BjA*rJV6|vqwP;|SXkwjcV1;O4 zh3IB&=w@wbVr}SRZD?R^=w@wbU{z>fRcK;W=wemqW)0|O4QOBuXn^`RK=+%V``ys} z2IzhhbiWIV-vGt$hT=Ct@w=e-4bb`~?3bFL^4(DO2B>)h)Vv95-T*akfSNZ!$D5$q z4N&bS=yg*+O8s!+VYdJY)=22}EGYFbq*e=%T1632_kn8f2h|>xXEn4h@~YuTsun=I zJCIQ|Lcz5hUou=Fgy`BT5?u1g8L#fN5$aPTUI;2o( z=yDxWs5I1h3{t38X!IncP_0nvNl@xBSoMs+o@WG9dni=93aVWP)vkqV*Fm*wq1t1R zNR5MbPvGpLDNyh+$fYJCmzs=RY9ex}h_a92?4l`9_UHto3MhLBWpBc^yMmoW4Hn)N z#5}EFS5d*PqJn6r6+}CY&M~TBCFo=&sADClVGM*=a)TWt3r8u#ciU|b47VY*`>9x{kHA6wYSzjtw9w00(X|EcjOf-O|*8; zcyoR1UDrofu$>=%f?8doXjcjO+nBz}bQ6(;#I!)>>uyn*EV7uTFG-hdRv_|d)XFsB zqdVHAgvn@Z4;pDktdB?=GH;iXdkLYn(4zXkW6j$&?Y^6xf6{eE(FIHo^?R+*HV;t^ zTR}hWpPjO(y+7-}>DK;Y{==EStn3O&fVhF(k1Lw5{`>Z9DbjclSlJ&pldCdn)$UpG7M$e_LI%J;g6cjxNiM%pL!4bo!Ax%KmK{@2fw( zY1X^R>xslcA^98YKhLwh6IS9w|NbVkq(1ZCAf7cF)c{%9hYn=VGObP1iA$y`OANj5 zinC!eV6_D+wua56FjnpjX`iix=`;Qytog{lW6@2O+Ps*)A-ab}+4vJ5J__{pTh#j> zh*F@)QhC(o!>fKo?w&pYM#G}3&1-OlJ2vlE2GljaBI;^Ss%A%;cg5S#|84SZ|8-8s zzH7aYjp_N=HU0+`?5=cy@eph*MUv{94U0vFd5P!g z*NQmtfP7V>`&6X(P7y^`VeG8T9>Lz0%xbgC-BecpKUsObqHo5IfoEIKHxxEwzXLtc zIkW0%V*k1Weg*bB^S1EL*f0J5m{cGu{kOWR&HJwF=`R@{(Dm+q^1f!SZICiHce7eLK1vk?p(gFVCPX$6_8Q@6N?7MzwdJs`rS_TaipeSM~Z)Ys{bx+SLr20aDk# z!=4OX+eoEmL1y+<@ko_tc#8yU<0#P8{Dg}3JpWP8$~&5cXk1o8e|Gon{fuCq5Z-`{ z%{r7a${jo%aP1MFZs$(sS+oycb60HcN!&G0Bh~48{Plk#K4jmLz3;oU#$D&ycggyg zh)hc#v)0EhZWampn9ZQwYrdKH&05>qkwz-5(*q>ss6)z8kH=FfvW~ibCwR}*ITisPPi07`D|R`}JCcW#;PDjM<`f|n zDM2bygjA#ismKtdA~i@wh9ecJK`JsFsmKsKo(hqTl;ZJJh=imR2}u$ONdXd);yjz3 z;yjz3AxKDuA|a_pLQ;!_WC#+HVMs_uAR!rsgk%J=?;%J?YLRKwA=4;f-bv zc~3L%HIn1@W+cLm@?G!3k}VY7UMSdn*K6@z??Rgz;=A6J(W#p6dKa2ii>`62V6%A( z@m=pi%Q^wc@&vT3S^2Vgt*4MO7ock`MAuq`u5}z<>s@%Qx8-@Qcj2`@4X^b!tm#6$ z)+gb$-i6otBy_U@Uh7?Wt#_fP4Ks3TI}a^|wzdv!?Krfx4d`iG@mG)J*E)wop6VUw zY0HpecOb>?K#JXp6uSc{b}Lfs4z#kJXk|N*Xpcc3I~lol6>{yd$hE7GYfnV3-HJZ8 z0qJ%t((P7su#=H-Pt22Xk3q&gCL`lEFZ5Y?c7wC>>;`A$*$vJ@H(QR~;H*5m!C85B zgR_u{w<8m8M?XoWKjcD-(kxceYz0`s5IOasiP@X5x$9g>JbJU-Vjh(Yw$yC-6n@LfahX zo%~jWwz&{(a{^!V;rOC=;fp>3U-T}t(1rM-cj1dZ9AEUVJYV!Ke9=R+(}nn=*W!!b zg{C^h7rhpJbs@g!!|_G$LU&z=FZwim(bMR$L-g1M_@Z~=i=NKYX@~ftccIx1@kMXL z7kxaw=+p2;@4^>-U$hi$87+mKI9G@EJH!_~m3QJ?3SaazzUW;>gNN++H(t$^MBN~woG$O5N zL^{xjwBnII8ofvrdXZN2B4g2|PezyCfk*mibR->kq&K1|=|HdEiN2&2kMu}u(t&op z43G2{G$+(6FQg zoJX3NDm3tu(Z_V4j~R>4`AB@u8_~_QqMND0=X?|znhtdGW%!&oqOEB~GvA5MrWKvd zSbWZ#(9ciC+q@AiP6zt=$#|QOMx)b#pLr8{omTWZ9q4sB(Cf6KrJsy;rxh*zWVG~? z@hxvgS3eow@y6PI6J%>P514z-T0ZBeHFp zw;rR&?k=Og3qX$6t2)bbgXmsCz@%Mh%6eT-KU;$<=IX;nxCS~Sj>7{?JLLpp?M-yMiQCa zJlb85OzaPFt~IsCe|Y=sap|~VHfC*%*kb_M*q)R&XD~OnL3bJ^#;uqdubgoVxN6 zb&3&t2uKHOdWG+*)h%}ZlKyAuXDPo#ZoewvpbUa?rOYa^#m%iFS(|v zQ!cc^BA%NECSxQwlEB!x9RtRg$=%IAwPCbjo8A<=JMwZA?Tb7HO#fmMyC(UDKQG## zsK@HvPEadyHO!^y$4yrEf@rA4Vt-e%bhFIL%DScH;|;R0KBv31J#!@%4Z*Hc{(M$e zIscjQ>V8&mMn)NpY&XNUQ<3enHP{^c0$vV7YOB%#qm^nvZKj5^M9S(g~>6i^ljzECK9kw7Z~~ z=h2%j8k3oEE4C-|99_MY{{Qc9&-5PARStd~5q9_UkbSrI^xAc2O&j;JzD9u}KAdM; zS_OMAjf2uTl}35=KcdolXGDP_8XBdRC!QW^lC>N$KRKSX%$>|>&P*E-{Z;5+k?wI! z0diNJq3phElXUDJoxl5C*>#SWbnK_D2QP*N@?3b|E%yGZ=#0xH|C;SKNb<#6Zbq4u5JGFIt}-CNVwjR z)ZiLYIGv}pV)8zIZ-d$!|mE*k96;8&)nvAXm8x%dD;oj@&fI9 z=X*)*Zx8ZHWH)}OSE=3Ur@Rr`g|73O6Q?E4^v2oV(wk_zN^g>OlIMDpwR=3@n_?E- z-c;K$deh97+ncUk;?>@a#Px|AyxA6G%$ut{-d$d|?b*Efwjc8rh>D+}Ia2&U?`H27 zdG}iM%R=uC?+%ynzN2`czD{huQxeC!G>-|*<4;uPNzG;`xRt$)6;D;>o%bg-9Zc zkwlgti7dfCq>#H(%3Uc#9$B8}A5zX8@^pvpRy+6j_o$tFeZ@BPzpHzeK zD9?BoScJU-x?8p0%=6x8Ce10MCLmThI6y)=5$miRU&)4&AOh-OnkH1u!cc7kk zAdRekA+q}RJX!sC-h}a3hZiBcUKCmc)m~)R9muYGkzIEpyIzd!x)<4X2eRwlJlS>c zF0$*+JlS<8vTGmNb#I>Rx)<4X2eRv4WY-7h$*vb8yY9vRDUw?EBDLW(}~bqYy!FOupGB-M+Er5#DCd-EjK z9+K(tjo1u67Gq|iM` zp%oQXaZ!7aLRTS$?m-H@A5!RgC~q~k`#ngZ=Rt+{hYI%~g`R?^R~lLL1nBY2wd$>3XEo)zI`dq|*zLPWM3B zry!kPfONVF>2y7`z76U07$|-Z(&>3fr>mg;^-%x)kWSaL225cMs75-ykd+{fjJk^T zpa&WCLS)nvSR2wvs(V-=s*zOJvrhD|PLv?8?m=E%kGy&z^6Glz)eDhVFGOB_P{LxO zFGOBlg}k~4d3C*3m+$DQyHl%6J@V>>$g9U7uU@2+BOY>#h@Rd{6ppF*Ff`!9Fd3h- zRy-MMh=n>Ae}<{}GmIq)$55hh)Z*1J4X=iM@M@Tq7lmUO9%*y%ZJ3I0!#JXF48_Bt z4mo)dQ8!c#5M1=Q#10uDwOP@ks-=!FmHMXx^}z5WRF z`XlgF7=dKC5uoxKSeE(tFTLvYH)l*>p0PJzM=_I5#k*Zrff!}C1X_1?>%h&(t z^B3j6D%_q*YHVMscz#M>wT+9VYSnZC@91lhQ_Ou$@#EU<^L}& znEjgVd@X`u=1+1V;@8F zyV34clGrWEH(A{CIr*RZ=g##X9o2WAcy5%qvQLL=!;{39ohc6Li{U?rgW4#b=_=!c z#Pi%7-V$yS|MDHvvxnc8CjF=49u#j~oWa{#`TwM~etROJU0|(RI!W>1Ek?el*3MLV zQFxKkFNR-Ks+L{C+E#j1c$K23esgq) z!n*|D9p0_--&c%z4~@R3v1sq8@yOB}ZYJVad>ffC8kiX2|0a0iX2 z|0a0K`|V65gd0C3y}a`JA}(Fg1-*(A`~=Y15yM} zJrZuZ5$lhUaMML_(*b;R5qxw4KDrT$k3ycg{P+cp@Xn1`dHC?o`{a3^rif@T8s51P zn~nl_=OpI~6u>(t;F`z4HK*X3v)-u|>0mTmb0fAJ1@Oy_SZy@JFPFkE*TOFkhhHv( zUv7q9E`?uihF`9QUv7q9u7zK&fnOd5zg!EyJRDBB8BVzdPPrLQxfD*h86LS79=Qx2 zxfve00UmiAJaRKU@^Er!!3owZHB|Gg}1GR ztF6wr!hn}d!$sD?MV7-q*1^elYTljl5#(-~org16INV z4uJ=(ga@pH2Ta5LrQ!SP;QONZYIX2^jo5(H!uK^|12PJ}uNJA= z8qE2^P||oU^Q4d_Wao?G8D5I@q-f2Al`c*rHSP8Lif}MAQK&b0oZi<~y`eL7CdZt_ zetIr1)syutJt4R0$+$i7ea%9lN)}44X6J`^yq7*FczC>vzH03!yq$O9tH$k0?TiRt zpF3GSt64XPu^NV~g9+BbBdzDP(P`hT8~Oo06@|Zuvi2MR-7a&P80zrvPKM9Yf`K=N!FT>wZ>zu@mXtag{z zW)&!66^NqVRIv&aL;s7Q{Ap-@ntNXf%^wQQkD}gGAtPym=2t@Pi=gvq=zKBsyox8L z5=veLC6A)p426bQLBoq<$@2k{bC~405{f+(id_Z8j-uN{6uS^wT?LgcLVD2zeU75o zR6(DMq0DJ0b1{^8D6)zsD03B*xdO^u1Z9pQ+6?769s)Hkf*Plx#!)PrDyVTWGKq*9 z7a@mef*Mysfs3KQX=v|osBRR+CW?L#MX_l@-p~XcZb0791T`*#4yU2RQ52gd-lZW> z<7#9K)pashts>+XIE!-*mlxKL}Sa0zKRwV z=zE@|^$E{Tiu7Dy=Wt4XA4q23;#w=;tyJV#e^3wx!;B?W;Xc zJ4#EXG4m4V8=N@bSDV3Cy|nfLN#EkQ2uq(OqUs56t^clQRgiosDSDiIBQ?gAq>f3Q zES-VxI-wg*XE&}@?UQvPV<-;xd{-qZaiPv5yjXjcwDv0hAluu23^&T6R%ffaeb}Xp zh8JzYetC>2!;NmNPEY)no1haD?{ibwnatJBBuRbCt%3LZbol2);MR(-OS;dBqbqWs(_7r+&Jiaz%3YCYNsMt{7HuEr zt`;ZO>aLNTa+a$} zCEX|fXSMsDxSh|qhxNXk<(}m2_$lwjQ+gLJaz7JK@+J2RJ^xp^=f!Vqaxd{zzoqB- ze)mV6Q|eb;^}q5B)vp_JdB3mc9UuP=yc4BOT&M4O`ktXNDzft5{Cnx3Z3gnJi2A*F z59a2>$K~xX-!gB1R}|M27nR>sepBJA`ffAG zKE+REp~AQLJ*jwF@ysk#_-+h+kS$mA2DoQc_xTuCFbL!if;&FOm1Uvmf9V-&FEqI5 zZGE>F?a=SF#itgpE6^a_6bs#YG#VyrQaXnH-eGsN5qtcrd8=ezm%( z`1Yc-LD8qEQT1B%ZMEM;?O8m(W{_?z>MD8-XhG59qNUjqMJuvU`AsF21Fxg#sBG?_ zGkJWRd!j)_>-5#!D2?X@=xm#z!9Ih{P|=mDe{InXN^jQpwxT=x)toKdeMJvOxZGS7 zJ>IWWUT;O4RpS}U&4acUy&TO3c%G8nm~;0x_ap+2bcUTM^L#{nJ>+Q+OJwrg?D6t`~acK-)6+_p>(2X&~n_^E%@!i>{r1+sY_t7k* zaXpcPBPe>yisN@CcaL&VNn1&0$#^SYvZ$oDWO)`Uelv#N0eY>Raxap{Sr z>x|Nro^7~1=sa>amR<>-(PvxI|9E`4yPA9cfF7jYBWmSw>cvl8>DJPhGuLa-YuQ#y z-!RCYy3)5}-1ZpSL9U*cvWo2UQWnp1`B~X=WzCe+vr#rSh9DSj6;&Rb-%r5UKUj$B2 z*()*hI#B;>v$>D1_njDuuGi^zk{Y>dyIj93!Q~*k(&6B8V=<@=oaJ@`^& z5_BxN8ddokL5zjx;M{WSL)WX1K@pua=o)Z_zuqA2f6iqWbgu1!yhXwBN>^plMS`yb zzK+~efR6_Lz*7H~Vrj8e-Dln8&JU5h`(U};5c_D%vmh1^wOu8&emR&eb- zq}S=}7n@s~jZKu7~1eqb1PH}~a4 z>VC^o_avA_VA?3z1AHa$*MV1ae^=A~Wx$t!|BI#W&%nc?cBr)&%>LedjpQ<5#-Q`Q zZBB+qDfrdEM^kHcRMM`+{{fiGtVe$X_)ECPOThdSxtB1Kzi?eYruRAE=lH|a?{xo3 zrH9(SU1UQqv)pu78LazZ z_^Txs@Z6*R=Y<(cYt#KIdbBZog?7F|`~L#o9v9CGC>glzg2%|7*z@YZ)LN>$bB-_- zVE&WxFIZjoWqSNFJ$~8yw91S&*!vzO$@RVr+(O!D<^AZ2T5ROrFKO*5%k?XO7gBPe z!S3V;rsVfX%SaFP6=_%VX)Vm7)@`KoB8*oo{J&DS6ik}(Y2XJanP&d$z>H)bZZp{3 zz?@s;6wg~g3(o;R2Rw^(g=~BEY=b}2Fn&l29+)!9%mkhcJkozcEni_6_fyg<7|(&^ zE;EdOC@FVet=+En4HAa@yKyU8&6?OdT@ zU4g!mR|Su_q|jNZRh^*ml7M+GncSI@*>lQIg(*FME|K|dzmjSpJ?K|*0^t*`QZlzn zMMZ8fIl<0Vq>>g0KAd!|teG>tNgOQeIm8o?I#Vu<%*bWpKBEN`CbM zLPupEApc4Cw4z+tXNzKBe4zgy=)Y3W_k8y%X_UW>Qg69;$xAExg?(E16nJI)N_&l7 zi(Q?~w9;oR)k$5J>a;1Px=!Kst_WU8S_wvUNO(O*O5X%83a2uX3@Uw@^v9&4g2Fsc zdMl~ux-f@n23xIPrRrCy+Ei-qWvlfoSa&)*=bG`cx~J{lTFrSl$ef#$a$n_n$Sa*} znVr}Azw~Wazklm*%Wm5IsB8T2ap+EHjj~h}KNHUM5W_>5=GtJFZ6qkj1(igfV z`YeWT^bZAJucwA31))-1?dyy-bn7fV4l()jO5Hky3f0lGq>n1U!-?( zvHLr{E$6#U?(?!Lxz~LIdy;>VJ;_%0UFjoVbU$-1i^F+Fy0drOFQq#h?_O7Amrk$V zo8c|>#(K-V<=#ARg?Ed$Kzg)0ysP9-vDv#`dbDS~A9!2*l=p~V=+E}vHv4YBMV8$s z%Om0x{|o+dY0&QRSILXvm;SXnId7!@Lq$gE2z;GzcVsYBC)QmM%upPZ%XMnqmBCko zwTgXmUvOG*fAD}JqC6Np99$7R5+;Ic6wzczuvJk^#s|OFIdPMMzbg9oDv>eCfxT=0 zSH7Y8b&~bwe~|-caFGo749R0R>U*}vc*9=!o;PTlfp>AH-z)cEZa#cmu^d>>lAhUJ zrNPmY?}^Ii$%OOXg>c&VW#bPUzi{@Yvo8gj7DLCxPzBH=l^ck&xmUrnw-rvj}PbPmu}f-Z=m z4KbuSQ_e@{UPrlyRQ8ogmngka-%UWb3%Yy!{YowOQNy)qCdNN8{wbx;>KoN~K89Ww z^rn@Y(Aj$Dgf?p#Xm||G-ZNF7)~*Ss zsi!0KJ$AwxrF~f(xr;3~4{FWkPPl2ptrNCcd$f5Lso`FXp*seFa=GWmxfjNe?yg2~ zdA4Qx$kn(eE|+Wh`WVMps6j6Hdo2Zefb?Ot{&WmI2J~bWH)Xt_ZIh3md?L`67aK`u92G;k_KuHnH^k71 zK-S)1BRC@}r!k+$n6DIcG0?g}aF%;UTyA3o?dFQkjz^Fi3lOz3z3paHxho$~m-&lu zcWNy6SzEOCAn9h|UXGzhV(1y5#|O#nkBiD}jmzzred)xIx`J9?i=j7SXnPF3orUsh zkUOy`zm^4F|;X$ZU?%1H#HuO%RLc8Q7doGzI2kKUqKUJiJ{kH z=pCT*SY}_J3X+*lrp>kc;3r{Ws9-kyzdxf-v;xEw@{DNa}ap5+=eB?;6Zs3e9eV`z8` zeRwWwioNHvFYPGkC>s=Ni*q|;Xtw(8u^JsGwx8W`26#bJ`eNwvJ%konxhZQ@H&+9k zwLGOaZe@84<@z`x#vPk&Wzeyl7MIKQaaN2ww?8!HLXGE=7?-Ir<(jDFDK{ziddmg6 z)gXS|5kvQWDD*&_`)~|BX3)$Y-GghVZ0TH`gQh&0k87Wi&7JZzknVm|W9s^LzrD;_ z5mYth#Ta@w3$>?rg{H*0Q(n#HPT3YiZvma!ujO2g53=Rrmi_h>F|Htn;!#EA4By^p zDNsucweq{&u1F;ovrq$;W}<;KMLsV7>aPG_RAdv*S>-p7i7z| zAFq~AY+u)YM*G?Eh|bGGc0by0wqHOO8`Qp0-z#I>Z9uvoxpFhxcEz>p+EnI-{BrHo zomw}wd1^Ry($r}NwQtWtQ)_mG+V3=6`)1XMb0Zve@5|O`e=vsP_SzrG;--$xLhX+m zM0ubAa?T?ULIc7nRe~6qVc8 zTHa)Jf%HEET@^z&#?W;!l&cZtY8-j3+)lY1&LD789~Jcc)K{jyKI=9?cTc^4>O(nP z9`sbU9JnW}#?&0{**Nz-9f{aH`_gG8QwA9ZZIfUu7+}N?xvMVaVrD+_~;tB zesjI$aMQ9aPfKQ@+;vQSC&tA!qH@zJl{4ICcUqgidWSPmXAF&sq46;k<*rfLUT_92 zkDS#9{(2N*5eh;B4$}LhWxf&79T29BUG{#V_kCqtM z8n-goSi0hJxjq)exW)aUj-|S`6)}z)9Vd3I%kV>2Mk`&%%^e15z05#b8wH)$FLyB9 z#gyAPNV(gDyVLsZ*sSks9dC5J-Ep6w2Rk0=csvi6%iS86Gu$)O$l+ej=5`hd+HNfy z)LD^*I(B5C&M=0e+?guZ49+0Y2&D#f$50JW-EPWFipx!lp{VXM)mRPApoKBCB!&(H zIwmf6cjx_`o4^@#RSZ28L#F~=s@#D%tFd-KIm6}3<)HP0v=ZU0#yN2-7sOC*EF0pw zgN^jMxJItG8)ICq#_cgK2SqiW&}=+Pu0c-$JuB#WgJwP{=#?0HJ%-+lp%2e}$I9)i zJ3oE+%+n&?Y}Ol{?`3e#aMP3emQ1e{PR~$OZeQK~2%6p?XcXo8rk^%_jkP!3pxzid zYY(CElv|{_xf&79+G~qj>5QRVAG2dzPu$92V_6=T%k^U;hG=ER8M7~)v2MCnf$5j)d(HIgmEM%ak$bk~ z=0QhgbEm&J{nhD=U;y-(^#P9F20*#oJK}Qp#?S*m5BF<1S3`4?-?IIhm5y=Ito0ui zBcNQ1r9d+*o&JHY!_Pv~-vZK{b{mxZ}Z{sJiRoa#7u9W^A2t-;CF0 zyfMR|+hS<*AkcZ>Zcw>gZiKVC7soX=#!#-kD`VWtF?21EwKv!ZZjQ=n%=ag4#b47Q?Bgl;fh+0u^kErg0lr!k@xJK^E2h?T$BHY^=%XVvv_VjK> zbZy_}7z$&k2B=6S2Ilt1Mdj+^a$4K=T}WMn#>UX37@8SF(_$!BBg$RUua#w#%i#=) zaK{K*zwbFp`$LBfz^(09ZZ)|%+^N~zSv&To?b14)qB^W9a!9dL8J^Y`K{|GZ)S5G#t>V z80w872h^Z)197Um?|TEvS$ny1IVd?uD-q6Wl*Fx6#!zl7!{fSxjdXlmBiCD7jLX%S z9piFPRO1L;{c>^*Iu>Y+pg#DQ2WCDz^ImWUT^~b_?ICm)<*rfPT#X24?VTI9a$yYR z`nV*7DKc$ zt94fUtQNx!fXZUrj6t9{cjgCit`Ahuuf4e3tg5&i?eQxe;~HZqHaQF2q zcPF_y+=FrMj@hBn;@K7ABEwlP$I$ivOKQ|_7Fw6ZmBWndp4T_ZPwTyHttYuT2K z+Ga+d>*MVh7uSf&%^s_q8htMXniNBIG1MGGQSLGNy%3y1OJZns3{8unnLyo22dl9x zE_YZAMXjvY?+xG#S{p;B#?U!H7bvBitkKs8vT9Z;t7g9~kX5rE1hQ%tMpn&AW&iH2 z-zi&C!Lo2yDri@ z;IC#Lu4ZPgw%p)hJgKf`Zm*8YQ1@yZgTIm|r=7XIg}J?jxxK}FCcIl{{}yKT7H0Jp zTDXN?Zs96!p|!t~-ol*Uf*;-KR??3=^iHBybGpThZ?VQO7&!pr6$}C_E z3j+CVcnc`Afb#e{dJAY_0au#uLpYI=6KQQCtxd!)aU<8Yky;zUZ{)f*($_|=Ze#F* z`r1gWnvJxx(R%ccpx=#lb^hg6*Pj!mR>q%WE&KS$3AT6bK>9k6u^mW{k+nDHCr8uPpdz00_k;ape7lh8(@IG~z=aE;}L<^`eD-b(iqd!zjZ-pxPK`)YcR zyct(B=GBZL>*Z)yy_zws<~fP{5?3?BM>E3_-ME2kzh>vBRQ?9qxq({Os8?ZZgzi?- z-`PC7-|;^F&Pux9S&zbKaL^=-f=2vcUJxlmV%!SrUOg|^V31C>Euqs zw{ru}&jxZgaPKzI;|Ato15fD&%5UI4ZlKK#)~2YZ^|gU#l`{S^E9oz@GD1NIrp&<^Sbk~3JVR>Nd$_GH?uy_7rB9eYs(fFq zoyY_B3hPU-$}5#Gno4S}+-BOx-%@zhtE&+B{~s2e^lpdm;?&chSx~@bkC}^Jsr=CN(d;c~Ke4XZ&WlQ}c3u zv;IDo>+eIYiz(R$d=c40dlsb8e%`c)Gq}y*S=+ORx#&Q%<#R` zs$y14$Snbm-p3MJ7(xprw6KD=eucgDeg${@SKRRx-0{dqcLn8F(Ao<6T0vhKpI)wU z1=pDM?A1(qk!SC(tXA+t%ME_WNPcbai#+0NKHs9pf1}4vdW^h_I~ix>37qjdrr%Eb z?c@pR;!3-?imd0b^*i10vClAXWpKSPPjeMN=Pi4hyY@6CudtE0D|la;4fg8{ud|J; z?%SC%_9WcF{oRN1cDkYGHrkWmes3)+wI^XCPr|n;ndTayEnY-bws9X74^QLV#(ZXc zl^I*)FCBR-FXMXgXZEw6%yxBU)GA{pBj5Eh=BkVqB7g5WfvvxDf+LjvIcQV*1gp=S z;8?@^TP&sg9L4yvk~6>?eVd~*v<%a3sg2YBJ4QRle_fa@Ss{S`(&)$lr>$3{DqC+91` zEml&y2E$*hbG^jV0!J%&w95Yzn3c4%k~vvvtp(Rccxz3&Cu?CP{jRi;`1Q={Tg>XJ zz-Dz7v$`rc)nLZCiuqhc%d42xRkU+Vl+xNUnOwWZRbcKWUB&#b^29UvDJ$=n8s0CZ z?rPHgsJkD1?MJQsn5F#~^L~`6p-c^31UiPULPz zb2X73nPJtn8CwcwDZQ`s?M*)vo`0pU_g&AP$u}e^02WQqDSLKbp5twLj=S@m^(c(J zN0(YVDr0Zca}iAYBP5+jm7)W73r$grgtdwb|`%v z%IqCV%ZEZm52fWOrbg7)p*9ab&y`>9Yy3)oL;4?+SQ9%HQ=;C}UQfMOJKqg)jc&5*atFDi+)3_i_eJ*=w@H5ckGh|`SKJ@< zn3a1Co}N;b_nh`p{Qrkc{Xnrenp~UC8}D^TyFNt#xWrxSzNH8N|HnO}xJz$~m8L?qqk4`$zXxcdPrJ;%NLrF#`TekuipNjoxH8(-qD;Vn3Z( zF@N6jMtAM}WlA4EaNZG(Zu>z8FKP6~_AXh_=pDA~;DZ{y4a-%=yMOtz0~@{9>8VPQ zLhKyXb~n#0bt~OE_XT&Iq7&S&lP#WAB=2{;Kxf)D+0KZ0NNO%dxv8$l9pa94r?_+7 zM)yzdHurt^xcd*qE%=icdPBWtZ%U?oN-=*&E9%00MOrw{ovH{3Us7z4e|8VJA1mU= zZxrJop~xL0ExH8l7r0@r#dWv^?i22K_ZfGdyVQNn-R^$io=`-D*WI5LvAx`t#T3#0n(-7X%E~x5LorTPxKFt=+~13rztMfi zJ>>pf5gUHz-t!8*;ocZq>nUHN6XVA#ZpR{bs9WRC)EOdID$c{5it(`7z2M$(?|Vhw z2t{%be@OXKSEtj0W-7YJVeSOC-d&^!LN~d)^pyTo@gRP$SSQ6^+8gK1$doU0^{&;; zQY?wXbxO%+-No)IMaH_@{i|Xiz2x3>fAvbdT5r6!uQ&6!9kVymRqdy*O0C%| z>2arjD%H1YZ=|bEJ~eg0st-#~`_SCo)?MBA=@U|y?v-@iDJQ3{TD=$2HK!f7Ds|o7 zNHrP9rEcCU>AEvdPTkhGH_}s9^`-9Yv-CdF2T30xeVlYN=`*BTNna*?jr0xDw@J5? z?$9t#D+u>SdWsq?DB3IOX{*;2RGhLG(o=2B!}msdx~N=1!(K^0y>3mxsMGgGdh)7M z3&x+mH`3GFrWCZDzBke{k3YSj^Yp!vo_c!0?9zFww2~VZIjVR|gOQJk!!j5tfw)G4hmuy17LvlB zyAt4H(gM;VQue$K$&C0NYhk2PU;HytOmU)y8%H{flwG4Z7sIp16<=j=vpDXfk}nRq zu5wZ39hG-f`TE>mwY9oIpYHgxy!w&qXRAM`si~P=v$*E4nhiB;YR*u4P0fqLT81qe zcI>dThFw1FjuG4Ime((=UsHc+{iV$}jI>>s^-w;5)Q-jYarq$`e8Nr#s`rxy{S;6OmvxCnE=LBB} z&JF%9I4?L~aj^bAxG=al_+qdj_=n(<;2(pH!Iu;R>oUd0xfJ zt_yw;{Od=Kdvwqej0wgD?g{PygI9uI1+NCb4qgj>6TBY$HrN*YE_fsO zeeh=Rhv2Q?kHOo)e+BOZe+u3W{v2!%{t~0>3sW@c? zVPRMl7KbHaX;>DPhZW(Fuu_rBs>159CL9(H4@ZRQur{m<>%)d{pRh4(3Y)``;izzQ z*bx7%Dbb|WV5;y3yvGWt(&^col>nx9N>U^3_ zI@#tQ6B`p>(n%h67S!cB+vy6O@bP6kqsC5J*E#EV>m1yB6W>kTm-t@dew|eFLE^88 z9f_Sum-Lc;GDwCxsJSyaBYAA{IGq@CLh{7qr<129_f7UB=O-5=_e(BJ?w>q3xioo* zPLeq(*_S*yxlX6ad_Va>@(0NWbr#o)$rlt|vN%)_36~w)Jds6MN0IwOKyag zj@O>PkW;D3wC6ljyZp7+@dn-gIYsfAs$3F3|~#zPn5^onl3rE%hf#$2HlX>mKyy`3v2@ z>*T5v-Oo9*>J9&N|8)0z|6KoE_on}x|GfKy|B_@Me+&wPCa*LY6|{OS!PH=uH(Brd zLT`p>*CAey-td*)e7%Vmc?T={-UHrJi@WEY8axs_<(+9~)p!@$Y3bfYIwAc`Z$tQn z@C)8Xowj(s_a&WD^Cjajx+`GX} zobqlAp9-JyzG)}ZcsJY0G~O*beY?QhlqgCRdEeHlGef-F64i+s?>lyCly_Gmok)9k zC+ZXR-aU3+ly|Srh#KSFr*mi~dJot+f8G!6L@4heJBP;mk)8kKJ**RG4)Fd}XHOsR zJ!0qTd5_zPP2LlU&m=zM{kxsXy$t~V?i>&GWCHY434}K!~cJc$iFqKLb_*IIk`6=mEg3Jo!72fN$`)mEv{Ihi5 zF7z*voyIl(_5Mx%t^OT46Y~N8VgE7zN&jhoi{$06`rG`s{CE8iv`Q5OWkFSt4jM(} zTeULH5CvZlEY^y&LNxk#twHOw-khzqpS;ZWZ-= zO_c6!QMDbSU`3)0}fnOjFCj@PML>k?<^SKF=c_foga z4RiOqk#3BeNL1$OZU$pPxm!T=6z)-Y5Vko^pf<-6JG0*c1wGvebSrKThe~%?dcuqfV4Cn zmfoL^Ovj`X)2ZonBDwX_)$C%cx+dBSu4RX5)pgN+aD8+D+z=fEH%5oRP3(`Z@Un-& z4~ST+`XNS>>K0UB)sKWoe7Y4bSoLGU+SF}A{9WBHSfaW^h;pkt1^Y^ONeX@SF| zZPh)f5vr9^2iz;!;6BL#_e(B#K!}lOQppRL4`xXLSWDnSO>HRz>qrrpE%m`1X#nO* zL$Izi0`sI8%$LSs0eXy@dMIsb3Z)rXB+bG4(gJKCUjrLT3D`(lg2mDbY|OsfYMRJN zU{h%WHj}ns2`sOgmT9t>$UJU$J?Ho%7W^D}BM|gxS~pi8xzoX2?&$Kg+G)^YSzB1?dOAC_e{ZlH0(U z@(b`4xgDG(zXWGXfACeJyD9VC9pD`K6*yP!1n0?J;C#HQ)GUyJ;6fP$E|R;!#Zn3` zk-^|nDFc_uJ>YV=7hEB~23N`uaFq-N&xCmmJrG5i9;oD`Yn7aIosyHTS8`%}Cnw#g zwVx3y&v4EzXx~e1K=n6 zAh=tpMct!+04sGQxL5xO?$d|B{W=Ofpbt~t6t5}zo{j-)=~%EfN<4i}$AQ^89?a1R zV6ILC>*}Lmo=yVubuw6>kAd}c3RtL9!6Kao*4M|u2Kofpkp0!^d-@bujCTWlPoD;x z-~&S6(`UhEcn#3<^iN=k&R~hQS~lUcqWDjA;M&FdXRwn#4}M=?058!O!Atce@CP~* zybSL$HQt|0%@sO}bzg7e+hVEC=8fBjwmq6?543762gmAbk8$=au$dSKzW-WY^ghobR>oxq7aa6T9B7 zzp63e`gNQb{S^&H#k3N-**jMjRFX2TP2*2@bgdVo9;h)Ds8N2e?Yq5Z72PYtWO6i8 zX2`PWIaw(y@lLHEUga{Pt#6N(hY0H{h*MiTdWZeAzaG7-=j-{=d+e`$DVp$&dULdn zUAFs0n~DB$SM*VcM7t+MpRLT4W=f;IM1`Cd?F+GX_lL-=2Z+X95FI4;c5!q#L}2~Y z$LnR2|8n~#P68EnrxX$mw3W1Vr@PbTWMVCMmQ!LEJ2^E@B%M?|Il$RM=W6DJ9CmQeN*iy3JT&TiwTQJGR&^ ztg!>`pgTk?0G|f`asK+}ROK)5Qxf&9zQRuSEW*(|GkvU;dx>oNvbOT^KJ}zfT8*+O zOI1lv=a(hN<*;VQ9m49rt@RN&U|EjBv(FYmq%tfW*KM-M-8 zv?&}>0-j4R;49G!j@+ZgyY(SGqk!97o#!WtiH21+}Q_ibo>4Nqj%z3 zwCmisHkcRJ0r6ppGI37zQQdG9$`R*tjD=i%uf9L8&Z!Y+S#dF#7dH-Rj=XE!G~8(h z=ElwaeV%cw=0C2gbAFr6{XACT4Jk$SH$@@$?d?>)=xrQjv9>Pc%U|!`CGP3pg*s5T zEA9mzsF8wxOA6nIH^NBOO5N-_DUnU1Uvli!9;0dA?O(4yyk3ts6mKe60zr zLDg0NZxnixIN#5R#uBas29xiSLDin8?X?qEGmJBH86_^RPC+~m+MCa{qWYSmj2AJR zyq~Fntr@_~F$kunlsTj92wO9R+2SZyk3(= zH)KR<%Bs92E4eyH2ez%Z8HhtId3mqfb#SAaKlTU4wPI4y$$p z*6b#%*blH?w_vqy#ai8lmAV7#bQf0XZmiKttk8W}p9fCBXs0!}_|#;XTBf$C1M`t% za!p;6M+AuiQ_mEdB2(WqfDLJ6icMqF#59E$@!Wlh`63qlH2Cfl^Wg0R3*Moc2frAu z?0C+E0&cv=SHO-}=>cENkB|6_CeR%AZj2cV&oj>4-#A51&rBsu1t%y6rnRMr1mM%Jhe)M@N z%l}SGI@XTH{~v2fSy;^58N7DFiT8mM?-=#R8Xge6{N>tKG$=X;#_${H2xpy8JGj<$ zb=_R|W4ghAQ!{7@kJ$PIszF^iXRJ(bV}}*Uw;8wLnWI{sXO8N4t2@f}Jad%idFCkJ z^UT^s#J#YG`(X_a!WRB#bQrHilHnU@@WZqC z%EkDzrTCoH!Y`~29*sG88P>(4FCVYFdU(ba;l0)X53@%2PBzAWv8gnZ=F$R>yb@_C zt?+C+3GcJEax!s<{zbkn|0<`-zeziRO%qr&;u67}35=P*lnD$O@rl~YcZg5aK|0F! zEf5*GgCECf%inTqix{dg&#- zrH|YoKb9NiCi#i_t^>;d4?}P3Cy*{81>InS< zy#F7Gku*vl*3l>cWAzaor{i@3TEL?^Nhj-LIt5i=nm(>i=#%;s`oPorj6SQ+@&8F? z5Mk+geL-K;m(UDe)>m|v&em78Twl{UI#=iEd~}3`x=0u65?zX-uv}N@N?oO|qb zoEa3u@}L;b35wzTpcpO)>fXX*;~|Vs8yLwQ?%%)S86pi#P!yOT)_tt}{K|d{vxOh= zbHa$9A4dEpVZ?75M*QYAN>&o(pxE{He^02Qe~Yv^v>R%k^@4-cNL&h+R|~$b4xC&L H&y4;71utqP diff --git a/Jetsnack/app/src/main/res/values-v23/font_certs.xml b/Jetsnack/app/src/main/res/values-v23/font_certs.xml new file mode 100644 index 0000000000..1ff72f47a4 --- /dev/null +++ b/Jetsnack/app/src/main/res/values-v23/font_certs.xml @@ -0,0 +1,31 @@ + + + + @array/com_google_android_gms_fonts_certs_dev + @array/com_google_android_gms_fonts_certs_prod + + + + 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 + + + diff --git a/Jetsnack/gradle/libs.versions.toml b/Jetsnack/gradle/libs.versions.toml index b5d5319724..a566afea33 100644 --- a/Jetsnack/gradle/libs.versions.toml +++ b/Jetsnack/gradle/libs.versions.toml @@ -53,6 +53,7 @@ spotless = "8.2.1" # @keep targetSdk = "36" version-catalog-update = "1.1.0" +uiTextGoogleFonts = "1.10.5" [libraries] accompanist-adaptive = { module = "com.google.accompanist:accompanist-adaptive", version.ref = "accompanist" } @@ -151,6 +152,7 @@ roborazzi-compose = { module = "io.github.takahirom.roborazzi:roborazzi-compose" roborazzi-rule = { module = "io.github.takahirom.roborazzi:roborazzi-junit-rule", version.ref = "roborazzi" } rometools-modules = { module = "com.rometools:rome-modules", version.ref = "rome" } rometools-rome = { module = "com.rometools:rome", version.ref = "rome" } +androidx-compose-ui-text-google-fonts = { group = "androidx.compose.ui", name = "ui-text-google-fonts", version.ref = "uiTextGoogleFonts" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } From a8fb37195c046e042fa6143dbf2ee860bb393c3b Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 23 Mar 2026 15:52:32 +0000 Subject: [PATCH 14/33] Implement adaptive button styling using mediaQuery and update previews This change introduces adaptive styling for buttons using the `mediaQuery` API, allowing the UI to adjust padding and shapes dynamically based on the hardware configuration (e.g., physical vs. virtual keyboard). ### Summary of Changes * **Adaptive Styling**: Implemented `mediaQuery` logic in `Styles.kt` to toggle button padding and shapes. Buttons now use smaller padding and medium shapes when a physical keyboard is detected. * **Feature Activation**: Enabled `ComposeUiFlags.isMediaQueryIntegrationEnabled` in `MainActivity` and preview utilities to support the experimental Media Query API. * **Preview Enhancements**: * Added `UiMediaScopeWrapper` in `PreviewWrapper.kt` to facilitate testing different `UiMediaScope` states (keyboard kind, pointer precision) in Compose Previews. * Introduced `KeyboardKindProvider` and `PointerPrecisionProvider` for parameterized previews. * **Button Updates**: Updated `Button.kt` previews to utilize `UiMediaScopeWrapper`, specifically adding dedicated desktop previews to simulate physical keyboard and fine pointer precision environments. --- .../com/example/jetsnack/ui/MainActivity.kt | 5 + .../example/jetsnack/ui/components/Button.kt | 46 +++++--- .../com/example/jetsnack/ui/theme/Styles.kt | 20 ++-- .../jetsnack/ui/utils/PreviewWrapper.kt | 105 ++++++++---------- 4 files changed, 93 insertions(+), 83 deletions(-) diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/MainActivity.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/MainActivity.kt index d2a1e96131..a6622c1af5 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/MainActivity.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/MainActivity.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalComposeUiApi::class) + package com.example.jetsnack.ui import android.appwidget.AppWidgetManager @@ -23,6 +25,8 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.annotation.RequiresApi +import androidx.compose.ui.ComposeUiFlags +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.glance.appwidget.GlanceAppWidgetManager import androidx.lifecycle.lifecycleScope import com.example.jetsnack.widget.RecentOrdersWidgetReceiver @@ -33,6 +37,7 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) + ComposeUiFlags.isMediaQueryIntegrationEnabled = true if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { lifecycleScope.launch(Dispatchers.Default) { setWidgetPreviews() 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 9f3937496c..7d27e1dfb9 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 @@ -32,6 +32,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.Modifier +import androidx.compose.ui.UiMediaScope import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.role @@ -43,6 +44,7 @@ import com.example.jetsnack.ui.theme.LoadingState import com.example.jetsnack.ui.theme.loadingState import com.example.jetsnack.ui.utils.DesktopPreviewWrapper import com.example.jetsnack.ui.utils.PhoneUiMediaScopeWrapper +import com.example.jetsnack.ui.utils.UiMediaScopeWrapper @Composable fun Button( @@ -81,7 +83,7 @@ fun Button( @Preview @Composable private fun ButtonPreview() { - JetsnackTheme { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { Button(onClick = {}) { Text(text = "Demo") } @@ -92,7 +94,7 @@ private fun ButtonPreview() { @Preview @Composable private fun ButtonPreviewLoading() { - JetsnackTheme { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { Button( onClick = {}, enabled = true, @@ -107,7 +109,7 @@ private fun ButtonPreviewLoading() { @Preview @Composable private fun ButtonPreviewDisabled() { - JetsnackTheme { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { Button( onClick = {}, enabled = false, @@ -117,32 +119,44 @@ private fun ButtonPreviewDisabled() { } } -@PreviewWrapperProvider(PhoneUiMediaScopeWrapper::class) -@Preview("default", "rectangle") -@Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) -@Preview("large font", "rectangle", fontScale = 2f) +@PreviewWrapperProvider(DesktopPreviewWrapper::class) +@Preview @Composable -private fun RectangleButtonPreview() { - JetsnackTheme { +private fun ButtonDesktopPreview() { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Physical, UiMediaScope.PointerPrecision.Fine) { Button( onClick = {}, - style = { - shape(RectangleShape) - }, ) { Text(text = "Demo") } } } - @PreviewWrapperProvider(DesktopPreviewWrapper::class) +@Preview +@Composable +private fun ButtonDesktopPreviewDisabled() { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Physical, UiMediaScope.PointerPrecision.Fine) { + Button( + onClick = {}, + enabled = false + ) { + Text(text = "Demo") + } + } +} + +@PreviewWrapperProvider(PhoneUiMediaScopeWrapper::class) @Preview("default", "rectangle") @Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) +@Preview("large font", "rectangle", fontScale = 2f) @Composable -private fun ButtonDesktopPreview() { - JetsnackTheme { +private fun RectangleButtonPreview() { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { Button( onClick = {}, + style = { + shape(RectangleShape) + }, ) { Text(text = "Demo") } @@ -151,3 +165,5 @@ private fun ButtonDesktopPreview() { + + 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 index f4374826f5..ac214855f2 100644 --- 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 @@ -45,6 +45,7 @@ import androidx.compose.ui.graphics.Shader import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.graphics.shadow.Shadow +import androidx.compose.ui.mediaQuery import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp @@ -119,6 +120,7 @@ data class Styles( val schemePrimary = Color(0xFF9685FF) val schemeInversePrimary = Color(0xFF9D8EFA) val schemeTertiary = Color(0xFFFFC8A4) + val contentColor = Color(0xff0E0066) background( ellipticalGradient( colors = listOf(schemePrimary, schemeTertiary), @@ -129,14 +131,16 @@ data class Styles( ), ) - contentColor(Color(0xff0E0066)) - /* if (mediaQuery { keyboardKind == UiMediaScope.KeyboardKind.Physical }) { - contentPaddingVertical(4.dp) - contentPaddingHorizontal(8.dp) - } else {*/ - contentPaddingVertical(8.dp) - contentPaddingHorizontal(24.dp) - /* }*/ + contentColor(contentColor) + if (mediaQuery { keyboardKind == UiMediaScope.KeyboardKind.Physical }) { + contentPaddingVertical(4.dp) + contentPaddingHorizontal(8.dp) + shape(shapes.medium) + } else { + contentPaddingVertical(8.dp) + contentPaddingHorizontal(24.dp) + shape(shapes.small) + } minSize(58.dp, 48.dp) textStyleWithFontFamilyFix(typography.labelLarge) diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt index 2ebb02b5b5..d829efb34e 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt @@ -1,9 +1,7 @@ -@file:OptIn(ExperimentalMediaQueryApi::class) +@file:OptIn(ExperimentalMediaQueryApi::class, ExperimentalComposeUiApi::class) package com.example.jetsnack.ui.utils - - import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -12,13 +10,14 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.LocalUiMediaScope import androidx.compose.ui.UiMediaScope +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewWrapper - +import com.example.jetsnack.ui.theme.JetsnackTheme class DesktopPreviewWrapper : PreviewWrapper { private val themeWrapper = ThemeWrapper() - private val desktopPreviewWrapper = DesktopUiMediaScopeWrapper() - private val lookaheadScopeWrapper = LookaheadScopeWrapper() @Composable @@ -27,71 +26,57 @@ class DesktopPreviewWrapper : PreviewWrapper { // followed by the environment/container wrapper. themeWrapper.Wrap { lookaheadScopeWrapper.Wrap { - desktopPreviewWrapper.Wrap { - content() - } + content() } } } } -/** - * This class is a workaround for UiMediaQuery not exposing previews just yet. - * - */ -@OptIn(ExperimentalComposeUiApi::class) -class DesktopUiMediaScopeWrapper: PreviewWrapper { - @Composable - override fun Wrap(content: @Composable (() -> Unit)) { - // Step 1: Enable the mediaQuery function - ComposeUiFlags.isMediaQueryIntegrationEnabled = true - BoxWithConstraints { - // Step 2: Define a custom object implementing the UiMediaScope interface. - val uiMediaScope = object : UiMediaScope { - override val keyboardKind: UiMediaScope.KeyboardKind - get() = UiMediaScope.KeyboardKind.Physical - override val windowPosture: UiMediaScope.Posture - get() = UiMediaScope.Posture.Flat - override val windowWidth = maxWidth // faking the width and height for previews - override val windowHeight = maxHeight - override val pointerPrecision = UiMediaScope.PointerPrecision.Fine - override val hasMicrophone = true - override val hasCamera = true - override val viewingDistance = UiMediaScope.ViewingDistance.Near - } +@Composable +fun UiMediaScopeWrapper( + keyboardKind: UiMediaScope.KeyboardKind, + pointerPrecision: UiMediaScope.PointerPrecision, + content: @Composable () -> Unit) { - // Step 3: Set the object to the LocalUiMediaScope. - CompositionLocalProvider(LocalUiMediaScope provides uiMediaScope) { - content() - } + ComposeUiFlags.isMediaQueryIntegrationEnabled = true + BoxWithConstraints { + val uiMediaScope = object : UiMediaScope { + override val keyboardKind: UiMediaScope.KeyboardKind + get() = keyboardKind + override val windowPosture: UiMediaScope.Posture + get() = UiMediaScope.Posture.Flat + override val windowWidth = maxWidth // faking the width and height for previews + override val windowHeight = maxHeight + override val pointerPrecision = pointerPrecision + override val hasMicrophone = true + override val hasCamera = true + override val viewingDistance = UiMediaScope.ViewingDistance.Near } - } -} -@OptIn(ExperimentalComposeUiApi::class) -class PhoneUiMediaScopeWrapper: PreviewWrapper { - @Composable - override fun Wrap(content: @Composable (() -> Unit)) { - // Step 1: Enable the mediaQuery function - ComposeUiFlags.isMediaQueryIntegrationEnabled = true - BoxWithConstraints { - // Step 2: Define a custom object implementing the UiMediaScope interface. - val uiMediaScope = object : UiMediaScope { - override val keyboardKind: UiMediaScope.KeyboardKind - get() = UiMediaScope.KeyboardKind.Virtual - override val windowPosture: UiMediaScope.Posture - get() = UiMediaScope.Posture.Flat - override val windowWidth = maxWidth // faking the width and height for previews - override val windowHeight = maxHeight - override val pointerPrecision = UiMediaScope.PointerPrecision.Blunt - override val hasMicrophone = true - override val hasCamera = true - override val viewingDistance = UiMediaScope.ViewingDistance.Near - } - // Step 3: Set the object to the LocalUiMediaScope. + JetsnackTheme { CompositionLocalProvider(LocalUiMediaScope provides uiMediaScope) { content() } } + } } + + +// Assuming KeyboardKind is your enum/class +@OptIn(ExperimentalMediaQueryApi::class) +class KeyboardKindProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf( + UiMediaScope.KeyboardKind.Physical, + UiMediaScope.KeyboardKind.Virtual, + ) +} +@OptIn(ExperimentalMediaQueryApi::class) +class PointerPrecisionProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf( + UiMediaScope.PointerPrecision.Fine, + UiMediaScope.PointerPrecision.Blunt, + UiMediaScope.PointerPrecision.Coarse, + UiMediaScope.PointerPrecision.None + ) +} \ No newline at end of file From b3ab8d98ce2caa0eaa6a1795229f3e32ae0476dc Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 24 Mar 2026 16:47:29 +0000 Subject: [PATCH 15/33] Refactor theme, styles, and UI components for design consistency This change updates the core design system of Jetsnack, including a revised color palette, updated typography, and improved component styling using the `styleable` modifier. ### Summary of Changes 1. **Theme and Styling**: * **Color Palette**: Introduced a new semantic color palette (e.g., `WarpCore`, `Nebula`, `VastOfNight`) and simplified gradient definitions to `gradient1`, `gradient2`, and `gradient3`. * **Typography**: Migrated all text styles to use `Instrument Sans`, removing the `Karla` font family. Adjusted headline and title sizes for better hierarchy. * **Styles**: Refactored `buttonStyle` and `filterChipStyle` to utilize theme-based colors and added detailed state animations for pressed, loading, and disabled states. 2. **Component Refinement**: * **Filters**: Updated `FilterBar` and chips with new shapes, borders, and typography. Replaced manual `Box` wrappers with `styleable` configurations. * **Cart**: Refined `CartItem` layout using `IntrinsicSize.Min` and updated `SnackImage` styling with `RoundedCornerShape`. * **Search**: Updated `SearchCategory` cards with a new aspect ratio, shadow effects, and ripple indications. * **Navigation**: Updated bottom navigation colors and implemented a gradient border for the selection indicator using `styleable`. 3. **Infrastructure and Cleanup**: * **Dependencies**: Updated `androidx-compose-bom` to `2026.03.00`, `android-material3` to `1.14.0-alpha10`, and `androidx-corektx` to `1.18.0`. * **Previews**: Added `SharedElementPreviewWrapper` to support shared transition testing in previews and simplified `@Preview` annotations across components. * **Utilities**: Removed unused gradient extension functions in `Gradient.kt`. 4. **Layout Adjustments**: * Updated `Feed` to use `systemBars` insets for top spacing. * Standardized `DestinationBar` height and updated delivery address typography. --- .../jetsnack/ui/SnackSharedElementKey.kt | 3 +- .../example/jetsnack/ui/components/Button.kt | 15 +- .../example/jetsnack/ui/components/Filters.kt | 64 ++---- .../jetsnack/ui/components/Gradient.kt | 21 +- .../ui/components/QuantitySelector.kt | 4 +- .../example/jetsnack/ui/components/Snacks.kt | 126 ++++------- .../jetsnack/ui/home/DestinationBar.kt | 10 +- .../java/com/example/jetsnack/ui/home/Feed.kt | 9 +- .../java/com/example/jetsnack/ui/home/Home.kt | 29 +-- .../com/example/jetsnack/ui/home/cart/Cart.kt | 72 +++--- .../jetsnack/ui/home/search/Categories.kt | 64 ++++-- .../jetsnack/ui/snackdetail/SnackDetail.kt | 131 +++-------- .../com/example/jetsnack/ui/theme/Color.kt | 213 +++++++++--------- .../com/example/jetsnack/ui/theme/Styles.kt | 60 +++-- .../com/example/jetsnack/ui/theme/Type.kt | 17 +- .../jetsnack/ui/utils/CommonPreviewWrapper.kt | 24 -- .../jetsnack/ui/utils/PreviewWrapper.kt | 29 +-- Jetsnack/gradle/libs.versions.toml | 6 +- 18 files changed, 388 insertions(+), 509 deletions(-) delete mode 100644 Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/CommonPreviewWrapper.kt diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/SnackSharedElementKey.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/SnackSharedElementKey.kt index c07ed5d027..811911f080 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/SnackSharedElementKey.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/SnackSharedElementKey.kt @@ -22,8 +22,7 @@ enum class SnackSharedElementType { Bounds, Image, Title, - Tagline, - Background, + Price, } object FilterSharedElementKey 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 7d27e1dfb9..ec5a3da765 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 @@ -38,12 +38,9 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewWrapperProvider import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.theme.LoadingState import com.example.jetsnack.ui.theme.loadingState -import com.example.jetsnack.ui.utils.DesktopPreviewWrapper -import com.example.jetsnack.ui.utils.PhoneUiMediaScopeWrapper import com.example.jetsnack.ui.utils.UiMediaScopeWrapper @Composable @@ -67,20 +64,21 @@ fun Button( role = Role.Button }, ) + .styleable(styleState, JetsnackTheme.styles.buttonStyle, style) .clickable( enabled = enabled, onClick = onClick, interactionSource = interactionSource, indication = null, - ) - .styleable(styleState, JetsnackTheme.styles.buttonStyle, style), + ), content = content, verticalAlignment = Alignment.CenterVertically, ) } -@PreviewWrapperProvider(PhoneUiMediaScopeWrapper::class) @Preview +@Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) +@Preview("large font", "rectangle", fontScale = 2f) @Composable private fun ButtonPreview() { UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { @@ -90,7 +88,6 @@ private fun ButtonPreview() { } } -@PreviewWrapperProvider(PhoneUiMediaScopeWrapper::class) @Preview @Composable private fun ButtonPreviewLoading() { @@ -105,7 +102,6 @@ private fun ButtonPreviewLoading() { } } -@PreviewWrapperProvider(PhoneUiMediaScopeWrapper::class) @Preview @Composable private fun ButtonPreviewDisabled() { @@ -119,7 +115,6 @@ private fun ButtonPreviewDisabled() { } } -@PreviewWrapperProvider(DesktopPreviewWrapper::class) @Preview @Composable private fun ButtonDesktopPreview() { @@ -131,7 +126,6 @@ private fun ButtonDesktopPreview() { } } } -@PreviewWrapperProvider(DesktopPreviewWrapper::class) @Preview @Composable private fun ButtonDesktopPreviewDisabled() { @@ -145,7 +139,6 @@ private fun ButtonDesktopPreviewDisabled() { } } -@PreviewWrapperProvider(PhoneUiMediaScopeWrapper::class) @Preview("default", "rectangle") @Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) @Preview("large font", "rectangle", fontScale = 2f) 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 64ad722db6..5c7d6c5227 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 @@ -24,17 +24,15 @@ import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding 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.shape.RoundedCornerShape 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 @@ -45,8 +43,6 @@ 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.TileMode import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -65,13 +61,14 @@ fun FilterBar( onShowFilters: () -> Unit, filterScreenVisible: Boolean, sharedTransitionScope: SharedTransitionScope, + modifier: Modifier = Modifier, ) { with(sharedTransitionScope) { LazyRow( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(start = 12.dp, end = 8.dp), - modifier = Modifier.heightIn(min = 56.dp), + modifier = modifier.heightIn(min = 56.dp), ) { item { AnimatedVisibility(visible = !filterScreenVisible) { @@ -86,12 +83,15 @@ fun FilterBar( ) { Icon( painterResource(R.drawable.ic_filter_list), - tint = JetsnackTheme.colors.brand, + tint = JetsnackTheme.colors.iconPrimary, contentDescription = stringResource(R.string.label_filters), - modifier = Modifier.diagonalGradientBorder( - colors = JetsnackTheme.colors.interactiveSecondary, - shape = CircleShape, - ), + modifier = Modifier + .styleable(null) { + contentPaddingHorizontal(2.dp) + minHeight(32.dp) + border(3.dp, Brush.linearGradient(colors.interactiveSecondary)) + shape(RoundedCornerShape(50)) + } ) } } @@ -131,37 +131,17 @@ fun FilterChip(filter: Filter, modifier: Modifier = Modifier, style: Style = Sty style = JetsnackTheme.styles.filterChipStyle then style, styleState = styleState, ) { - val innerBackgroundStyle = Style { - background(Color.Transparent) - pressed { - animate { - background( - Brush.horizontalGradient( - colors = colors.interactiveSecondary, - startX = 0f, - endX = 200f, - tileMode = TileMode.Mirror, - ), - ) - } - } - } - Box( - modifier = Modifier - .styleable(styleState, innerBackgroundStyle), - ) { - Text( - text = filter.name, - style = { - textStyleWithFontFamilyFix(typography.bodySmall) - }, - maxLines = 1, - modifier = Modifier.padding( - horizontal = 20.dp, - vertical = 6.dp, - ), - ) - } + Text( + text = filter.name, + style = { + textStyleWithFontFamilyFix(typography.labelSmall) + }, + maxLines = 1, + modifier = Modifier.padding( + horizontal = 20.dp, + vertical = 6.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 5709b8cecd..a27f8c0cdb 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 @@ -35,23 +35,4 @@ fun Modifier.contentTintDiagonalGradient(colors: List, blendMode: BlendMo brush = Brush.linearGradient(colors), blendMode = blendMode, ) -} - -fun Modifier.offsetGradientBackground(colors: List, width: Density.() -> Float, offset: Density.() -> Float = { 0f }) = drawBehind { - val actualOffset = offset() - - drawRect( - Brush.horizontalGradient( - colors = colors, - startX = -actualOffset, - endX = width() - actualOffset, - tileMode = TileMode.Mirror, - ), - ) -} - -fun Modifier.diagonalGradientBorder(colors: List, borderSize: Dp = 2.dp, shape: Shape) = border( - width = borderSize, - brush = Brush.linearGradient(colors), - shape = shape, -) +} \ 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 6940051e9b..539f89e2f4 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 @@ -47,7 +47,7 @@ fun QuantitySelector(count: Int, decreaseItemCount: () -> Unit, increaseItemCoun Text( text = stringResource(R.string.quantity), style = { - textStyleWithFontFamilyFix(typography.titleMedium) + textStyleWithFontFamilyFix(typography.labelLarge) contentColor(colors.textSecondary) fontWeight(FontWeight.Normal) }, @@ -69,7 +69,7 @@ fun QuantitySelector(count: Int, decreaseItemCount: () -> Unit, increaseItemCoun Text( text = "$it", style = { - textStyleWithFontFamilyFix(typography.titleSmall) + textStyleWithFontFamilyFix(typography.labelLarge) fontSize(18.sp) contentColor(colors.textPrimary) textAlign(TextAlign.Center) 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 2cd70db035..9cbec14ee3 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 @@ -28,10 +28,8 @@ import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.core.animateDp import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -51,7 +49,7 @@ 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.styleable +import androidx.compose.foundation.style.then import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable @@ -59,10 +57,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue 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.TileMode import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity @@ -71,7 +66,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import coil.request.ImageRequest @@ -90,6 +84,7 @@ 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 +import com.example.jetsnack.ui.utils.formatPrice private val HighlightCardWidth = 170.dp private val HighlightCardPadding = 16.dp @@ -114,8 +109,8 @@ fun SnackCollection( Text( text = snackCollection.name, style = { - textStyleWithFontFamilyFix(typography.titleLarge) - contentColor(colors.brand) + textStyleWithFontFamilyFix(typography.headlineSmall) + contentColor(colors.textPrimary) }, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -129,7 +124,7 @@ fun SnackCollection( ) { Icon( painter = painterResource(id = R.drawable.ic_arrow_back), - tint = JetsnackTheme.colors.brand, + tint = JetsnackTheme.colors.textPrimary, contentDescription = null, ) } @@ -160,8 +155,8 @@ private fun HighlightedSnacks( } val gradient = when ((index / 2) % 2) { - 0 -> JetsnackTheme.colors.gradient6_1 - else -> JetsnackTheme.colors.gradient6_2 + 0 -> JetsnackTheme.colors.gradient1 + else -> JetsnackTheme.colors.gradient2 } LazyRow( @@ -205,8 +200,7 @@ fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String start = 4.dp, end = 4.dp, bottom = 8.dp, - ), - + ) ) { val sharedTransitionScope = LocalSharedTransitionScope.current ?: throw IllegalStateException("No sharedTransitionScope found") @@ -225,8 +219,11 @@ fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String SnackImage( imageRes = snack.imageRes, contentDescription = null, + style = { + shape(RoundedCornerShape(percent = 50)) + }, modifier = Modifier - .size(120.dp) + .size(width = 100.dp, height = 80.dp) .sharedBounds( rememberSharedContentState( key = SnackSharedElementKey( @@ -293,10 +290,11 @@ private fun HighlightSnackItem( } JetsnackCard( style = Style { + background(colors.cardHighlightBackground) shape(RoundedCornerShape(roundedCornerAnimation)) clip(true) - border(1.dp, colors.uiBorder) - size(width = HighlightCardWidth, height = 250.dp) + border(1.dp, colors.cardHighlightBorder) + size(width = HighlightCardWidth, height = 200.dp) }, modifier = modifier .padding(bottom = 16.dp) @@ -328,69 +326,30 @@ private fun HighlightSnackItem( ) }) .fillMaxSize(), - ) { - Box( + SnackImage( + imageRes = snack.imageRes, + contentDescription = null, + style = { + shape(RoundedCornerShape(bottomEnd = 24.dp, bottomStart = 24.dp)) + }, modifier = Modifier - .height(160.dp) - .fillMaxWidth(), - ) { - // todo investigate this is not clipping properly to the container - val spannedBackgroundGradient = Style { - val left = index * cardWidthWithPaddingPx - val gradientOffset = left - (scrollProvider() / 3f) - - val brush = - Brush.horizontalGradient( - colors = gradient, - startX = -gradientOffset, - endX = (6 * cardWidthWithPaddingPx) - gradientOffset, - tileMode = TileMode.Mirror, - ) - background(brush) - } - Box( - modifier = Modifier - .sharedBounds( - rememberSharedContentState( - key = SnackSharedElementKey( - snackId = snack.id, - origin = snackCollectionId.toString(), - type = SnackSharedElementType.Background, - ), - ), - animatedVisibilityScope = animatedVisibilityScope, - boundsTransform = snackDetailBoundsTransform, - enter = fadeIn(nonSpatialExpressiveSpring()), - exit = fadeOut(nonSpatialExpressiveSpring()), - resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds(), - ) - .styleable(null, spannedBackgroundGradient) - .height(100.dp) - .fillMaxWidth() - ) - - SnackImage( - imageRes = snack.imageRes, - contentDescription = null, - modifier = Modifier - .sharedBounds( - rememberSharedContentState( - key = SnackSharedElementKey( - snackId = snack.id, - origin = snackCollectionId.toString(), - type = SnackSharedElementType.Image, - ), + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = snackCollectionId.toString(), + type = SnackSharedElementType.Image, ), - animatedVisibilityScope = animatedVisibilityScope, - exit = fadeOut(nonSpatialExpressiveSpring()), - enter = fadeIn(nonSpatialExpressiveSpring()), - boundsTransform = snackDetailBoundsTransform, - ) - .align(Alignment.BottomCenter) - .size(120.dp), - ) - } + ), + animatedVisibilityScope = animatedVisibilityScope, + exit = fadeOut(nonSpatialExpressiveSpring()), + enter = fadeIn(nonSpatialExpressiveSpring()), + boundsTransform = snackDetailBoundsTransform, + ) + .fillMaxWidth() + .size(120.dp), + ) Spacer(modifier = Modifier.height(8.dp)) @@ -423,7 +382,7 @@ private fun HighlightSnackItem( Spacer(modifier = Modifier.height(4.dp)) Text( - text = snack.tagline, + text = formatPrice(snack.price), style = { textStyleWithFontFamilyFix(typography.bodyLarge) contentColor(colors.textHelp) @@ -435,7 +394,7 @@ private fun HighlightSnackItem( key = SnackSharedElementKey( snackId = snack.id, origin = snackCollectionId.toString(), - type = SnackSharedElementType.Tagline, + type = SnackSharedElementType.Price, ), ), animatedVisibilityScope = animatedVisibilityScope, @@ -463,12 +422,13 @@ fun SnackImage( @DrawableRes imageRes: Int, contentDescription: String?, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + style: Style = Style ) { Surface( - style = { + style = Style { shape(CircleShape) - }, + } then style, modifier = modifier, ) { AsyncImage( @@ -496,7 +456,7 @@ fun SnackCardPreview() { snack = snack, onSnackClick = { _, _ -> }, index = 0, - gradient = JetsnackTheme.colors.gradient6_1, + gradient = JetsnackTheme.colors.gradient1, scrollProvider = { 0f }, ) } 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 b5c85d0df9..e9dd3f7023 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 @@ -25,6 +25,7 @@ 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.layout.height import androidx.compose.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -39,6 +40,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import com.example.jetsnack.R import com.example.jetsnack.ui.LocalNavAnimatedVisibilityScope import com.example.jetsnack.ui.LocalSharedTransitionScope @@ -70,14 +72,13 @@ fun DestinationBar(modifier: Modifier = Modifier) { ), ) { TopAppBar( - windowInsets = WindowInsets(0, 0, 0, 0), title = { Row { Text( text = "Delivery to 1600 Amphitheater Way", style = { - textStyleWithFontFamilyFix(typography.titleMedium) - contentColor(colors.textSecondary) + textStyleWithFontFamilyFix(typography.titleSmall) + contentColor(colors.textPrimary) textAlign(TextAlign.Center) }, maxLines = 1, @@ -92,7 +93,7 @@ fun DestinationBar(modifier: Modifier = Modifier) { ) { Icon( painter = painterResource(id = R.drawable.ic_expand_more), - tint = JetsnackTheme.colors.brand, + tint = JetsnackTheme.colors.brandSecondary, contentDescription = stringResource(R.string.label_select_delivery), ) @@ -104,6 +105,7 @@ fun DestinationBar(modifier: Modifier = Modifier) { .copy(alpha = AlphaNearOpaque), titleContentColor = JetsnackTheme.colors.textSecondary, ), + modifier = Modifier.height(48.dp) ) JetsnackDivider() } 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 4c9b29cc22..415c9fc651 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 @@ -28,9 +28,9 @@ import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.add import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsTopHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed @@ -112,7 +112,7 @@ private fun SnackCollectionList( item { Spacer( Modifier.windowInsetsTopHeight( - WindowInsets.statusBars.add(WindowInsets(top = 56.dp)), + WindowInsets.systemBars ), ) FilterBar( @@ -120,12 +120,13 @@ private fun SnackCollectionList( sharedTransitionScope = sharedTransitionScope, filterScreenVisible = filtersVisible, onShowFilters = onFiltersSelected, + modifier = Modifier.padding(top = 48.dp) ) } itemsIndexed(snackCollections) { index, snackCollection -> if (index > 0) { JetsnackDivider(style = { - height(2.dp) + height(1.dp) }) } 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 d2265268ba..39807ece31 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 @@ -40,6 +40,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.styleable import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -48,6 +50,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember 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.Shape import androidx.compose.ui.graphics.TransformOrigin @@ -82,6 +85,7 @@ 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.colors import com.example.jetsnack.ui.theme.typography import java.util.Locale @@ -168,8 +172,8 @@ fun JetsnackBottomBar( currentRoute: String, navigateToRoute: (String) -> Unit, modifier: Modifier = Modifier, - color: Color = JetsnackTheme.colors.iconPrimary, - contentColor: Color = JetsnackTheme.colors.iconInteractive, + color: Color = JetsnackTheme.colors.brandLight, + contentColor: Color = JetsnackTheme.colors.iconPrimary, ) { val routes = remember { tabs.map { it.route } } val currentSection = tabs.first { it.route == currentRoute } @@ -197,9 +201,9 @@ fun JetsnackBottomBar( val selected = section == currentSection val tint by animateColorAsState( if (selected) { - JetsnackTheme.colors.iconInteractive - } else { JetsnackTheme.colors.iconInteractiveInactive + } else { + JetsnackTheme.colors.iconInteractiveInactive.copy(alpha = 0.9f) }, label = "tint", ) @@ -227,7 +231,7 @@ fun JetsnackBottomBar( selected = selected, onSelected = { navigateToRoute(section.route) }, animSpec = springSpec, - modifier = BottomNavigationItemPadding + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) .clip(BottomNavIndicatorShape), ) } @@ -403,16 +407,15 @@ private fun MeasureScope.placeTextAndIcon( } @Composable -private fun JetsnackBottomNavIndicator( - strokeWidth: Dp = 2.dp, - color: Color = JetsnackTheme.colors.iconInteractive, - shape: Shape = BottomNavIndicatorShape, -) { +private fun JetsnackBottomNavIndicator() { Spacer( modifier = Modifier .fillMaxSize() - .then(BottomNavigationItemPadding) - .border(strokeWidth, color, shape), + .styleable(null) { + shape(BottomNavIndicatorShape) + border(3.dp, Brush.linearGradient(colors.interactiveMask)) + externalPadding(horizontal = 16.dp, vertical = 8.dp) + } ) } @@ -420,8 +423,6 @@ private val TextIconSpacing = 2.dp private val BottomNavHeight = 56.dp private val BottomNavLabelTransformOrigin = TransformOrigin(0f, 0.5f) private val BottomNavIndicatorShape = RoundedCornerShape(percent = 50) -private val BottomNavigationItemPadding = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) - @Preview @Composable private fun JetsnackBottomNavPreview() { 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 5576b38f06..d540e6b2bb 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 @@ -14,9 +14,14 @@ * limitations under the License. */ +@file:OptIn(ExperimentalMediaQueryApi::class) + package com.example.jetsnack.ui.home.cart import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background @@ -25,6 +30,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets @@ -34,6 +40,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBars @@ -47,13 +54,17 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.Modifier +import androidx.compose.ui.UiMediaScope.KeyboardKind import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.LastBaseline +import androidx.compose.ui.layout.LookaheadScope import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -69,6 +80,8 @@ 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.LocalNavAnimatedVisibilityScope +import com.example.jetsnack.ui.LocalSharedTransitionScope import com.example.jetsnack.ui.components.Button import com.example.jetsnack.ui.components.JetsnackDivider import com.example.jetsnack.ui.components.Text @@ -82,7 +95,10 @@ 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.shapes import com.example.jetsnack.ui.theme.typography +import com.example.jetsnack.ui.utils.SharedElementPreviewWrapper +import com.example.jetsnack.ui.utils.UiMediaScopeWrapper import com.example.jetsnack.ui.utils.formatPrice import kotlin.math.roundToInt @@ -162,13 +178,13 @@ private fun CartContent( text = stringResource(R.string.cart_order_header, snackCountFormattedString), style = { textStyleWithFontFamilyFix(typography.titleLarge) - contentColor(colors.brand) + contentColor(colors.textPrimary) }, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier .heightIn(min = 56.dp) - .padding(horizontal = 24.dp, vertical = 4.dp) + .padding(horizontal = 24.dp) .wrapContentHeight(), ) } @@ -224,8 +240,7 @@ private fun SwipeDismissItemBackground(progress: Float) { Column( modifier = Modifier .background(JetsnackTheme.colors.uiBackground) - .fillMaxWidth() - .fillMaxHeight(), + .fillMaxWidth(), horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.Center, ) { @@ -233,7 +248,7 @@ private fun SwipeDismissItemBackground(progress: Float) { val padding: Dp by animateDpAsState( if (progress < 0.5f) 4.dp else 0.dp, label = "padding", ) - BoxWithConstraints( + Box( Modifier .fillMaxWidth(progress), ) { @@ -241,13 +256,12 @@ private fun SwipeDismissItemBackground(progress: Float) { modifier = Modifier .padding(padding) .fillMaxWidth() - .height(maxWidth) .align(Alignment.Center), shape = RoundedCornerShape(percent = ((1 - progress) * 100).roundToInt()), color = JetsnackTheme.colors.error, ) { Box( - modifier = Modifier.fillMaxSize(), + modifier = Modifier, contentAlignment = Alignment.Center, ) { // Icon must be visible while in this width range @@ -306,7 +320,7 @@ fun CartItem( .fillMaxWidth() .clickable { onSnackClick(snack.id, "cart") } .background(JetsnackTheme.colors.uiBackground) - .padding(horizontal = 24.dp), + .padding(horizontal = 20.dp), ) { Row( modifier = Modifier.fillMaxWidth(), @@ -314,20 +328,21 @@ fun CartItem( SnackImage( imageRes = snack.imageRes, contentDescription = null, - modifier = Modifier - .padding(vertical = 16.dp) - .size(100.dp), + style = { + shape(shapes.small) + externalPaddingVertical(8.dp) + size(100.dp) + } ) Column( modifier = Modifier - .weight(1f) .padding(start = 16.dp), ) { Row(modifier = Modifier.fillMaxWidth()) { Text( text = snack.name, style = { - textStyleWithFontFamilyFix(typography.titleMedium) + textStyleWithFontFamilyFix(typography.bodyLarge) contentColor(colors.textSecondary) }, modifier = Modifier @@ -336,7 +351,7 @@ fun CartItem( ) IconButton( onClick = { removeSnack(snack.id) }, - modifier = Modifier.padding(top = 12.dp), + modifier = Modifier.offset(x = 12.dp) ) { Icon( painter = painterResource(id = R.drawable.ic_close), @@ -348,7 +363,7 @@ fun CartItem( Text( text = snack.tagline, style = { - textStyleWithFontFamilyFix(typography.bodyLarge) + textStyleWithFontFamilyFix(typography.bodySmall) contentColor(colors.textHelp) }, modifier = Modifier.padding(end = 16.dp), @@ -372,12 +387,12 @@ fun CartItem( count = orderLine.count, decreaseItemCount = { decreaseItemCount(snack.id) }, increaseItemCount = { increaseItemCount(snack.id) }, - modifier = Modifier.alignBy(LastBaseline), + modifier = Modifier.alignBy(LastBaseline) ) } } } - JetsnackDivider() + JetsnackDivider(/*modifier = Modifier.padding(top = 8.dp)*/) } } @@ -388,7 +403,7 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi text = stringResource(R.string.cart_summary_header), style = { textStyleWithFontFamilyFix(typography.titleLarge) - contentColor(colors.brand) + contentColor(colors.textPrimary) }, maxLines = 1, overflow = TextOverflow.Ellipsis, @@ -500,13 +515,18 @@ private fun CheckoutBar(modifier: Modifier = Modifier) { @Composable private fun CartPreview() { JetsnackTheme { - Cart( - orderLines = SnackRepo.getCart(), - removeSnack = {}, - increaseItemCount = {}, - decreaseItemCount = {}, - inspiredByCart = SnackRepo.getInspiredByCart(), - onSnackClick = { _, _ -> }, - ) + UiMediaScopeWrapper { + SharedElementPreviewWrapper { + Cart( + orderLines = SnackRepo.getCart(), + removeSnack = {}, + increaseItemCount = {}, + decreaseItemCount = {}, + inspiredByCart = SnackRepo.getInspiredByCart(), + onSnackClick = { _, _ -> }, + ) + } + } } } + 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 f17b2eaeed..e2b0f9d9fd 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 @@ -19,6 +19,7 @@ package com.example.jetsnack.ui.home.search import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio @@ -30,12 +31,21 @@ 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.foundation.style.Style +import androidx.compose.foundation.style.rememberUpdatedStyleState +import androidx.compose.foundation.style.styleable +import androidx.compose.foundation.style.then +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.layout.Layout import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Constraints @@ -69,23 +79,25 @@ private fun SearchCategoryCollection(collection: SearchCategoryCollection, index text = collection.name, style = { textStyleWithFontFamilyFix(typography.titleLarge) - contentColor(colors.textPrimary) + contentColor(colors.textSecondary) }, modifier = Modifier .heightIn(min = 56.dp) - .padding(horizontal = 24.dp, vertical = 4.dp) + .padding(horizontal = 16.dp, vertical = 4.dp) .wrapContentHeight(), ) - VerticalGrid(Modifier.padding(horizontal = 16.dp)) { - val gradient = when (index % 2) { - 0 -> JetsnackTheme.colors.gradient2_2 - else -> JetsnackTheme.colors.gradient2_3 + VerticalGrid(Modifier.padding(horizontal = 8.dp)) { + val borderColor = when (index % 2) { + 0 -> Color(0xFF8BDEBE) + else -> Color(0xffFFC8A4) } collection.categories.forEach { category -> SearchCategory( category = category, - gradient = gradient, - modifier = Modifier.padding(8.dp), + modifier = Modifier.padding( 4.dp), + style = { + border(1.dp, borderColor) + } ) } } @@ -94,25 +106,32 @@ private fun SearchCategoryCollection(collection: SearchCategoryCollection, index } private val MinImageSize = 134.dp -private val CategoryShape = RoundedCornerShape(10.dp) +private val CategoryShape = RoundedCornerShape(24.dp) private const val CategoryTextProportion = 0.55f @Composable -private fun SearchCategory(category: SearchCategory, gradient: List, modifier: Modifier = Modifier) { +private fun SearchCategory(category: SearchCategory, + modifier: Modifier = Modifier, + style: Style = Style) { + val interactionSource = remember { MutableInteractionSource() } + val styleState = rememberUpdatedStyleState(interactionSource) Layout( modifier = modifier - .aspectRatio(1.45f) - .shadow(elevation = 3.dp, shape = CategoryShape) - .clip(CategoryShape) - .background(Brush.horizontalGradient(gradient)) - .clickable { /* todo */ }, + .aspectRatio(1.85f) + .styleable(styleState, Style { + shape(CategoryShape) + clip(true) + background(colors.uiBackground) + dropShadow(Shadow(color = Color(0xffE5E1E2), radius = 8.dp)) + textStyleWithFontFamilyFix(typography.titleMedium) + contentColor(Color(0xff005C5E)) + } then style) + .clickable(interactionSource = interactionSource, + indication = ripple() + ) { /* todo */ }, content = { Text( text = category.name, - style = { - textStyleWithFontFamilyFix(typography.titleMedium) - contentColor(colors.textSecondary) - }, modifier = Modifier .padding(4.dp) .padding(start = 8.dp), @@ -120,6 +139,9 @@ private fun SearchCategory(category: SearchCategory, gradient: List, modi SnackImage( imageRes = category.imageRes, contentDescription = null, + style = { + shape(RoundedCornerShape(topStartPercent = 48)) + }, modifier = Modifier.fillMaxSize(), ) }, @@ -160,7 +182,9 @@ private fun SearchCategoryPreview() { name = "Desserts", imageRes = R.drawable.desserts, ), - gradient = JetsnackTheme.colors.gradient3_2, + style = { + border(1.dp, Color(0xFF8BDEBE)) + } ) } } 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 7ae75aa3e7..ead453826e 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 @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(ExperimentalSharedTransitionApi::class, ExperimentalAnimationApi::class) +@file:OptIn(ExperimentalSharedTransitionApi::class, ExperimentalAnimationApi::class, ExperimentalMediaQueryApi::class) package com.example.jetsnack.ui.snackdetail @@ -25,12 +25,7 @@ import androidx.compose.animation.EnterExitState import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateDp -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn @@ -47,6 +42,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -73,13 +69,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.drawWithCache -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.layout.Layout import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource @@ -104,7 +96,6 @@ import com.example.jetsnack.ui.SnackSharedElementKey import com.example.jetsnack.ui.SnackSharedElementType 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.Surface import com.example.jetsnack.ui.components.Text import com.example.jetsnack.ui.components.QuantitySelector @@ -112,22 +103,23 @@ 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.SharedElementPreviewWrapper +import com.example.jetsnack.ui.utils.UiMediaScopeWrapper import com.example.jetsnack.ui.utils.formatPrice import kotlin.math.max import kotlin.math.min private val BottomBarHeight = 56.dp private val TitleHeight = 128.dp -private val GradientScroll = 180.dp +private val GradientScroll = 120.dp private val ImageOverlap = 115.dp -private val MinTitleOffset = 56.dp +private val MinTitleOffset = 14.dp private val MinImageOffset = 12.dp private val MaxTitleOffset = ImageOverlap + MinTitleOffset + GradientScroll private val ExpandedImageSize = 300.dp -private val CollapsedImageSize = 150.dp +private val CollapsedImageSize = 130.dp private val HzPadding = Modifier.padding(horizontal = 24.dp) fun spatialExpressiveSpring() = spring( @@ -183,7 +175,6 @@ fun SnackDetail(snackId: Long, origin: String, upPress: () -> Unit) { .background(color = JetsnackTheme.colors.uiBackground), ) { val scroll = rememberScrollState(0) - Header(snack.id, origin = origin) Body(related, scroll) Title(snack, origin) { scroll.value } Image(snackId, origin, snack.imageRes) { scroll.value } @@ -193,64 +184,6 @@ fun SnackDetail(snackId: Long, origin: String, upPress: () -> Unit) { } } -@Composable -private fun Header(snackId: Long, origin: String) { - val sharedTransitionScope = LocalSharedTransitionScope.current - ?: throw IllegalArgumentException("No Scope found") - val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current - ?: throw IllegalArgumentException("No Scope found") - - with(sharedTransitionScope) { - val brushColors = JetsnackTheme.colors.tornado1 - - val infiniteTransition = rememberInfiniteTransition(label = "background") - val targetOffset = with(LocalDensity.current) { - 1000.dp.toPx() - } - val offset by infiniteTransition.animateFloat( - initialValue = 0f, - targetValue = targetOffset, - animationSpec = infiniteRepeatable( - tween(50000, easing = LinearEasing), - repeatMode = RepeatMode.Reverse, - ), - label = "offset", - ) - Spacer( - modifier = Modifier - .sharedBounds( - rememberSharedContentState( - key = SnackSharedElementKey( - snackId = snackId, - origin = origin, - type = SnackSharedElementType.Background, - ), - ), - animatedVisibilityScope = animatedVisibilityScope, - boundsTransform = snackDetailBoundsTransform, - enter = fadeIn(nonSpatialExpressiveSpring()), - exit = fadeOut(nonSpatialExpressiveSpring()), - resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds(), - ) - .height(280.dp) - .fillMaxWidth() - .blur(40.dp) - .drawWithCache { - val brushSize = 400f - val brush = Brush.linearGradient( - colors = brushColors, - start = Offset(offset, offset), - end = Offset(offset + brushSize, offset + brushSize), - tileMode = TileMode.Mirror, - ) - onDrawBehind { - drawRect(brush) - } - }, - ) - } -} - @Composable private fun SharedTransitionScope.Up(upPress: () -> Unit) { val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current @@ -268,13 +201,13 @@ private fun SharedTransitionScope.Up(upPress: () -> Unit) { exit = scaleOut(tween(20)), ) .background( - color = Neutral8.copy(alpha = 0.32f), + color = JetsnackTheme.colors.textPrimary.copy(alpha = 0.32f), shape = CircleShape, ), ) { Icon( painter = painterResource(id = R.drawable.ic_arrow_back), - tint = JetsnackTheme.colors.iconInteractive, + tint = JetsnackTheme.colors.uiBackground, contentDescription = stringResource(R.string.label_back), ) } @@ -451,17 +384,6 @@ private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { fontSize(20.sp) }, modifier = HzPadding - .sharedBounds( - rememberSharedContentState( - key = SnackSharedElementKey( - snackId = snack.id, - origin = origin, - type = SnackSharedElementType.Tagline, - ), - ), - animatedVisibilityScope = animatedVisibilityScope, - boundsTransform = snackDetailBoundsTransform, - ) .wrapContentWidth(), ) Spacer(Modifier.height(4.dp)) @@ -473,6 +395,17 @@ private fun Title(snack: Snack, origin: String, scrollProvider: () -> Int) { contentColor(colors.textPrimary) }, modifier = HzPadding + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = origin, + type = SnackSharedElementType.Price, + ), + ), + animatedVisibilityScope = animatedVisibilityScope, + boundsTransform = snackDetailBoundsTransform, + ) .animateEnterExit( enter = fadeIn() + slideInVertically { -it / 3 }, exit = fadeOut() + slideOutVertically { -it / 3 }, @@ -501,7 +434,7 @@ private fun Image( CollapsingImageLayout( collapseFractionProvider = collapseFractionProvider, - modifier = HzPadding.statusBarsPadding(), + modifier = Modifier, ) { val sharedTransitionScope = LocalSharedTransitionScope.current ?: throw IllegalStateException("No sharedTransitionScope found") @@ -512,7 +445,11 @@ private fun Image( SnackImage( imageRes = imageRes, contentDescription = null, + style = { + shape(RoundedCornerShape(percent = 50)) + }, modifier = Modifier + .aspectRatio(1.3f) .sharedBounds( rememberSharedContentState( key = SnackSharedElementKey( @@ -629,11 +566,15 @@ private fun CartBottomBar(modifier: Modifier = Modifier) { @Preview("large font", fontScale = 2f) @Composable private fun SnackDetailPreview() { - JetsnackPreviewWrapper { - SnackDetail( - snackId = 1L, - origin = "details", - upPress = { }, - ) + JetsnackTheme{ + SharedElementPreviewWrapper { + UiMediaScopeWrapper { + SnackDetail( + snackId = 1L, + origin = "details", + upPress = { }, + ) + } + } } } 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 918312fb87..21b2ef6b37 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 @@ -27,33 +27,33 @@ import androidx.compose.ui.graphics.Color */ @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 gradient1: List, + val gradient2: List, + val gradient3: List, val brand: Color, + val brandLight: 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 interactivePrimary: List = gradient2, + val interactiveSecondary: List = gradient1, + val interactiveMask: List = gradient3, + val interactiveDisabled: Color, + val interactiveDisabledText: Color, 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 cardHighlightBackground: Color, + val cardHighlightBorder: Color, val isDark: Boolean, ) /** @@ -111,58 +111,6 @@ fun debugColors(darkTheme: Boolean, debugColor: Color = Color.Magenta) = ColorSc onTertiaryFixedVariant = debugColor, ) -val Shadow11 = Color(0xff001787) -val Shadow10 = Color(0xff00119e) -val Shadow9 = Color(0xff0009b3) -val Shadow8 = Color(0xff0200c7) -val Shadow7 = Color(0xff0e00d7) -val Shadow6 = Color(0xff2a13e4) -val Shadow5 = Color(0xff4b30ed) -val Shadow4 = Color(0xff7057f5) -val Shadow3 = Color(0xff9b86fa) -val Shadow2 = Color(0xffc8bbfd) -val Shadow1 = Color(0xffded6fe) -val Shadow0 = Color(0xfff4f2ff) - -val Ocean11 = Color(0xff005687) -val Ocean10 = Color(0xff006d9e) -val Ocean9 = Color(0xff0087b3) -val Ocean8 = Color(0xff00a1c7) -val Ocean7 = Color(0xff00b9d7) -val Ocean6 = Color(0xff13d0e4) -val Ocean5 = Color(0xff30e2ed) -val Ocean4 = Color(0xff57eff5) -val Ocean3 = Color(0xff86f7fa) -val Ocean2 = Color(0xffbbfdfd) -val Ocean1 = Color(0xffd6fefe) -val Ocean0 = Color(0xfff2ffff) - -val Lavender11 = Color(0xff170085) -val Lavender10 = Color(0xff23009e) -val Lavender9 = Color(0xff3300b3) -val Lavender8 = Color(0xff4400c7) -val Lavender7 = Color(0xff5500d7) -val Lavender6 = Color(0xff6f13e4) -val Lavender5 = Color(0xff8a30ed) -val Lavender4 = Color(0xffa557f5) -val Lavender3 = Color(0xffc186fa) -val Lavender2 = Color(0xffdebbfd) -val Lavender1 = Color(0xffebd6fe) -val Lavender0 = Color(0xfff9f2ff) - -val Rose11 = Color(0xff7f0054) -val Rose10 = Color(0xff97005c) -val Rose9 = Color(0xffaf0060) -val Rose8 = Color(0xffc30060) -val Rose7 = Color(0xffd4005d) -val Rose6 = Color(0xffe21365) -val Rose5 = Color(0xffec3074) -val Rose4 = Color(0xfff4568b) -val Rose3 = Color(0xfff985aa) -val Rose2 = Color(0xfffdbbcf) -val Rose1 = Color(0xfffed6e2) -val Rose0 = Color(0xfffff2f6) - val Neutral8 = Color(0xff121212) val Neutral7 = Color(0xde000000) val Neutral6 = Color(0x99000000) @@ -172,63 +120,106 @@ val Neutral3 = Color(0x1fffffff) val Neutral2 = Color(0x61ffffff) val Neutral1 = Color(0xbdffffff) val Neutral0 = Color(0xffffffff) - -val FunctionalRed = Color(0xffd00036) -val FunctionalRedDark = Color(0xffea6d7e) -val FunctionalGreen = Color(0xff52c41a) val FunctionalGrey = Color(0xfff6f6f6) val FunctionalDarkGrey = Color(0xff2e2e2e) const val AlphaNearOpaque = 0.95f +// New Color Palette +val WarpCoreLight = Color(0xFF9685FF) +val WarpCoreDark = Color(0xFF311EA9) +val NebulaLight = Color(0xFFE4E1FA) +val NebulaDark = Color(0xFF3229CD) +val VastOfNightLight = Color(0xFF0E0066) +val VastOfNightDark = Color(0xFFFDFCFC) +val ScienceLight = Color(0xFF8BDEBE) +val ScienceDark = Color(0xFF297258) +val TricorderLight = Color(0xFFBEFBE3) +val TricorderDark = Color(0xFF1EBE7F) +val DeepSpaceLight = Color(0xFF005C5E) +val DeepSpaceDark = Color(0xFFA4F1F2) +val StarburstLight = Color(0xFFFFC8A4) +val StarburstDark = Color(0xFFCC9570) +val TeleportLight = Color(0xFFFFECE9) +val TeleportDark = Color(0xFFD16A5A) +val SalamanderLight = Color(0xFF544337) +val SalamanderDark = Color(0xFFF1DED1) +val RedShirtLight = Color(0xFFFFDAD6) +val RedShirtDark = Color(0xFF93000A) +val CommandLight = Color(0xFF93000A) +val CommandDark = Color(0xFFFFDAD6) +val CloudLight = Color(0xFFFCF8F9) +val CloudDark = Color(0xFF141314) +val ShuttleLight = Color(0xFF222B2B) +val ShuttleDark = Color(0xFFE3E8E8) +val OutlineLight = Color(0xFF7F7573) +val OutlineDark = Color(0xFF9A8E8C) +val OutlineVariantLight = Color(0xFFD1C4C1) +val OutlineVariantDark = Color(0xFF4E4543) +val Warp10Light = Color(0xFFFFFFFF) +val Warp10Dark = Color(0xFF0E0E0F) +val Warp5Light = Color(0xFFF7F3F3) +val Warp5Dark = Color(0xFF1C1B1C) +val WarpLight = Color(0xFFEBE7E7) +val WarpDark = Color(0xFF2A2A2A) + +val gradient1Light = listOf(NebulaLight, ScienceLight, DeepSpaceLight) +val gradient1Dark = listOf(NebulaDark, ScienceDark, DeepSpaceDark) +val gradient2Light = listOf(WarpCoreLight, StarburstLight) +val gradient2Dark = listOf(WarpCoreDark, StarburstDark) +val gradient3Light = listOf(NebulaLight, WarpCoreLight, VastOfNightLight) +val gradient3Dark = listOf(NebulaDark, WarpCoreDark, VastOfNightDark) + 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), + brand = WarpCoreLight, + brandLight = NebulaLight, + brandSecondary = ScienceLight, + uiBackground = CloudLight, + uiBorder = OutlineLight, + uiFloated = WarpLight, + textSecondary = VastOfNightLight, + textHelp = ShuttleLight, + textInteractive = ShuttleLight, + textPrimary = DeepSpaceLight, + textLink = DeepSpaceLight, + iconPrimary = ShuttleLight, + iconSecondary = ShuttleLight, + iconInteractive = WarpCoreLight, + iconInteractiveInactive = ShuttleLight, + interactiveDisabled = WarpLight, + interactiveDisabledText = ShuttleLight, + error = CommandLight, + gradient1 = gradient1Light, + gradient2 = gradient2Light, + gradient3 = gradient3Light, + cardHighlightBackground = TeleportLight, + cardHighlightBorder = StarburstLight, 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), + brand = WarpCoreDark, + brandLight = NebulaDark, + brandSecondary = ScienceDark, + uiBackground = CloudDark, + uiBorder = OutlineDark, + uiFloated = WarpDark, + textPrimary = DeepSpaceDark, + textSecondary = VastOfNightDark, + textHelp = ShuttleDark, + textInteractive = ShuttleDark, + textLink = DeepSpaceDark, + iconPrimary = ShuttleDark, + iconSecondary = ShuttleDark, + iconInteractive = WarpCoreDark, + iconInteractiveInactive = ShuttleDark, + interactiveDisabled = WarpDark, + interactiveDisabledText = ShuttleDark, + error = CommandDark, + gradient1 = gradient1Dark, + gradient2 = gradient2Dark, + gradient3 = gradient3Dark, + cardHighlightBackground = TeleportDark, + cardHighlightBorder = StarburstDark, 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 index ac214855f2..5f7da6bd60 100644 --- 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 @@ -116,14 +116,9 @@ fun ellipticalGradient( data class Styles( val buttonStyle: Style = Style { shape(shapes.small) - // todo extract colors from theme - val schemePrimary = Color(0xFF9685FF) - val schemeInversePrimary = Color(0xFF9D8EFA) - val schemeTertiary = Color(0xFFFFC8A4) - val contentColor = Color(0xff0E0066) background( ellipticalGradient( - colors = listOf(schemePrimary, schemeTertiary), + colors = colors.interactivePrimary, radiusXPercent = 1.3f, radiusYPercent = 0.7232f, centerXPercent = 0.4f, @@ -131,7 +126,8 @@ data class Styles( ), ) - contentColor(contentColor) + contentColor(colors.textInteractive) + minSize(58.dp, 48.dp) if (mediaQuery { keyboardKind == UiMediaScope.KeyboardKind.Physical }) { contentPaddingVertical(4.dp) contentPaddingHorizontal(8.dp) @@ -141,37 +137,37 @@ data class Styles( contentPaddingHorizontal(24.dp) shape(shapes.small) } - minSize(58.dp, 48.dp) textStyleWithFontFamilyFix(typography.labelLarge) - dropShadow(Shadow(color = schemePrimary, offset = DpOffset(x = 2.dp, y = 4.dp), radius = 12.dp)) - innerShadow(Shadow(color = schemeInversePrimary, offset = DpOffset(x = (-6).dp, (-4).dp), radius = 4.dp)) + dropShadow(Shadow(color = colors.brand, offset = DpOffset(x = 2.dp, y = 4.dp), radius = 12.dp)) + innerShadow(Shadow(color = colors.brand, offset = DpOffset(x = (-6).dp, (-4).dp), radius = 4.dp)) + pressed { animate { - background(Brush.radialGradient(listOf(schemePrimary, schemePrimary))) - dropShadow(Shadow(color = schemePrimary, offset = DpOffset(x = 0.dp, y = 0.dp), radius = 0.dp)) - innerShadow(Shadow(color = schemeInversePrimary, offset = DpOffset(x = (0).dp, (0).dp), radius = 0.dp)) + background(Brush.radialGradient(listOf(colors.brand, colors.brand))) + dropShadow(Shadow(color = colors.brand, offset = DpOffset(x = 0.dp, y = 0.dp), radius = 0.dp)) + innerShadow(Shadow(color = colors.brand, offset = DpOffset(x = (0).dp, (0).dp), radius = 0.dp)) } } loading { animate { background( ellipticalGradient( - colors = listOf(schemeTertiary, schemePrimary), + colors = colors.interactivePrimary.reversed(), radiusXPercent = 1.3f, radiusYPercent = 0.7232f, centerXPercent = 0.4f, centerYPercent = 0.55f, ), ) - dropShadow(Shadow(color = schemePrimary, offset = DpOffset(x = 0.dp, y = 0.dp), radius = 0.dp)) - innerShadow(Shadow(color = schemeInversePrimary, offset = DpOffset(x = (0).dp, (0).dp), radius = 0.dp)) + dropShadow(Shadow(color = colors.brand, offset = DpOffset(x = 0.dp, y = 0.dp), radius = 0.dp)) + innerShadow(Shadow(color = colors.brand, offset = DpOffset(x = (0).dp, (0).dp), radius = 0.dp)) } } disabled { animate { - background(Color(0xffDDD9D9)) - contentColor(Color(0xFF939090)) + background(colors.interactiveDisabled) + contentColor(colors.interactiveDisabledText) // reset shadow dropShadow(Shadow(color = Color.Transparent, offset = DpOffset(x = 0.dp, y = 0.dp), radius = 0.dp)) innerShadow(Shadow(color = Color.Transparent, offset = DpOffset(x = (0).dp, (0).dp), radius = 0.dp)) @@ -213,14 +209,30 @@ data class Styles( val filterChipStyle: Style = Style { shape(shapes.small) background(colors.uiBackground) - contentColor(colors.textSecondary) - border(1.dp, Brush.linearGradient(colors.interactiveSecondary)) - // todo elevation = 2.dp, + contentColor(colors.textInteractive) + border(2.dp, Brush.linearGradient(colors.interactiveSecondary)) + minHeight(32.dp) + textStyleWithFontFamilyFix(typography.labelSmall) + pressed { + animate { + val gradient = ellipticalGradient( + colors = colors.interactivePrimary, + radiusXPercent = 1.3f, + radiusYPercent = 0.7232f, + centerXPercent = 0.4f, + centerYPercent = 0.55f, + ) + border(2.dp, gradient) + background(gradient) + } + } selected { animate { - background(colors.brandSecondary) - contentColor(Color.Black) - border(1.dp, Color.Transparent) + background(colors.brand) + contentColor(colors.textSecondary) + border(2.dp, colors.brand) + dropShadow(Shadow(color = colors.brand, radius = 6.dp, offset = DpOffset(0.dp, 2.dp) )) + innerShadow(Shadow(color = colors.brand, offset = DpOffset((-6).dp, (-8).dp), radius = 8.dp)) } } }, diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Type.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Type.kt index 0cbffbbb7f..d70bc903ad 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Type.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Type.kt @@ -38,11 +38,6 @@ val instrumentSansFontFamily = FontFamily( Font(googleFont = instrumentSansFontName, fontProvider = provider), ) -private val Karla = FontFamily( - Font(R.font.karla_regular, FontWeight.Normal), - Font(R.font.karla_bold, FontWeight.Bold), -) - val Typography = Typography( displayLarge = TextStyle( fontFamily = instrumentSansFontFamily, @@ -66,9 +61,9 @@ val Typography = Typography( ), headlineMedium = TextStyle( fontFamily = instrumentSansFontFamily, - fontSize = 30.sp, - fontWeight = FontWeight.SemiBold, - lineHeight = 37.sp, + fontSize = 26.sp, + fontWeight = FontWeight(500), + lineHeight = 36.sp, ), headlineSmall = TextStyle( fontFamily = instrumentSansFontFamily, @@ -90,14 +85,14 @@ val Typography = Typography( letterSpacing = 0.15.sp, ), titleSmall = TextStyle( - fontFamily = Karla, + fontFamily = instrumentSansFontFamily, fontSize = 14.sp, fontWeight = FontWeight.Bold, lineHeight = 24.sp, letterSpacing = 0.1.sp, ), bodyLarge = TextStyle( - fontFamily = Karla, + fontFamily = instrumentSansFontFamily, fontSize = 16.sp, fontWeight = FontWeight.Normal, lineHeight = 28.sp, @@ -118,7 +113,7 @@ val Typography = Typography( fontSize = 14.sp, ), bodySmall = TextStyle( - fontFamily = Karla, + fontFamily = instrumentSansFontFamily, fontSize = 12.sp, fontWeight = FontWeight.Bold, lineHeight = 16.sp, diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/CommonPreviewWrapper.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/CommonPreviewWrapper.kt deleted file mode 100644 index f9b6b14e84..0000000000 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/CommonPreviewWrapper.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.jetsnack.ui.utils - -import androidx.compose.runtime.Composable -import androidx.compose.ui.layout.LookaheadScope -import androidx.compose.ui.tooling.preview.PreviewWrapper -import com.example.jetsnack.ui.theme.JetsnackTheme - -class ThemeWrapper: PreviewWrapper { - @Composable - override fun Wrap(content: @Composable (() -> Unit)) { - JetsnackTheme { - content() - } - } -} -class LookaheadScopeWrapper: PreviewWrapper { - @Composable - override fun Wrap(content: @Composable (() -> Unit)) { - LookaheadScope { - content() - } - } - -} \ No newline at end of file diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt index d829efb34e..a660a74bf0 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt @@ -2,6 +2,8 @@ @file:OptIn(ExperimentalMediaQueryApi::class, ExperimentalComposeUiApi::class) package com.example.jetsnack.ui.utils +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.SharedTransitionLayout import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -14,19 +16,20 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewWrapper +import com.example.jetsnack.model.SnackRepo +import com.example.jetsnack.ui.LocalNavAnimatedVisibilityScope +import com.example.jetsnack.ui.LocalSharedTransitionScope +import com.example.jetsnack.ui.home.cart.Cart import com.example.jetsnack.ui.theme.JetsnackTheme -class DesktopPreviewWrapper : PreviewWrapper { - private val themeWrapper = ThemeWrapper() - private val lookaheadScopeWrapper = LookaheadScopeWrapper() - - @Composable - override fun Wrap(content: @Composable () -> Unit) { - // Nest the wrappers: Theme is usually the outermost layer, - // followed by the environment/container wrapper. - themeWrapper.Wrap { - lookaheadScopeWrapper.Wrap { - content() +@Composable +fun SharedElementPreviewWrapper(content: @Composable () -> Unit) { + SharedTransitionLayout { + AnimatedVisibility(true) { + CompositionLocalProvider(LocalSharedTransitionScope provides this@SharedTransitionLayout) { + CompositionLocalProvider(LocalNavAnimatedVisibilityScope provides this@AnimatedVisibility) { + content() + } } } } @@ -34,8 +37,8 @@ class DesktopPreviewWrapper : PreviewWrapper { @Composable fun UiMediaScopeWrapper( - keyboardKind: UiMediaScope.KeyboardKind, - pointerPrecision: UiMediaScope.PointerPrecision, + keyboardKind: UiMediaScope.KeyboardKind = UiMediaScope.KeyboardKind.Virtual, + pointerPrecision: UiMediaScope.PointerPrecision = UiMediaScope.PointerPrecision.Blunt, content: @Composable () -> Unit) { ComposeUiFlags.isMediaQueryIntegrationEnabled = true diff --git a/Jetsnack/gradle/libs.versions.toml b/Jetsnack/gradle/libs.versions.toml index a566afea33..888859e409 100644 --- a/Jetsnack/gradle/libs.versions.toml +++ b/Jetsnack/gradle/libs.versions.toml @@ -1,12 +1,12 @@ [versions] accompanist = "0.37.3" -android-material3 = "1.14.0-alpha09" +android-material3 = "1.14.0-alpha10" androidGradlePlugin = "9.0.1" androidx-activity-compose = "1.12.4" androidx-appcompat = "1.7.1" -androidx-compose-bom = "2026.02.00" +androidx-compose-bom = "2026.03.00" androidx-core-splashscreen = "1.2.0" -androidx-corektx = "1.17.0" +androidx-corektx = "1.18.0" androidx-glance = "1.2.0-rc01" androidx-lifecycle = "2.8.2" androidx-lifecycle-compose = "2.10.0" From 65ff9a4a9ca9274140734aa5d8e530b5e8c0212a Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 24 Mar 2026 19:44:51 +0000 Subject: [PATCH 16/33] Introduce the SharedBoundsMorph.kt - to perform morphing shape animations when performing shared element animations. --- Jetsnack/app/build.gradle.kts | 1 + .../example/jetsnack/ui/components/Snacks.kt | 95 +++++++++--- .../jetsnack/ui/snackdetail/SnackDetail.kt | 65 +++++++-- .../jetsnack/ui/utils/SharedBoundsMorph.kt | 138 ++++++++++++++++++ Jetsnack/gradle/libs.versions.toml | 6 +- Jetsnack/settings.gradle.kts | 24 +-- 6 files changed, 281 insertions(+), 48 deletions(-) create mode 100644 Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/SharedBoundsMorph.kt diff --git a/Jetsnack/app/build.gradle.kts b/Jetsnack/app/build.gradle.kts index 6ae96b772d..74cd68447a 100644 --- a/Jetsnack/app/build.gradle.kts +++ b/Jetsnack/app/build.gradle.kts @@ -108,6 +108,7 @@ android { dependencies { implementation(libs.androidx.compose.ui.text.google.fonts) + implementation(libs.androidx.graphics.shapes) val composeBom = platform(libs.androidx.compose.bom) implementation(composeBom) androidTestImplementation(composeBom) 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 9cbec14ee3..428e6ed1dc 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, ExperimentalFoundationStyleApi::class) +@file:OptIn(ExperimentalSharedTransitionApi::class, ExperimentalFoundationStyleApi::class, ExperimentalMaterial3ExpressiveApi::class) package com.example.jetsnack.ui.components @@ -25,7 +25,10 @@ import androidx.compose.animation.EnterExitState import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionLayout import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateDp +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.clickable @@ -50,11 +53,13 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.foundation.style.Style import androidx.compose.foundation.style.then +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider 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.Color @@ -67,6 +72,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp +import androidx.graphics.shapes.Morph import coil.compose.AsyncImage import coil.request.ImageRequest import com.example.jetsnack.R @@ -84,7 +90,10 @@ 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 +import com.example.jetsnack.ui.utils.SnackPolygons +import com.example.jetsnack.ui.utils.asShape import com.example.jetsnack.ui.utils.formatPrice +import com.example.jetsnack.ui.utils.sharedBoundsRevealWithShapeMorph private val HighlightCardWidth = 170.dp private val HighlightCardPadding = 16.dp @@ -216,24 +225,46 @@ fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String }) .padding(8.dp), ) { + val sharedContentState = rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = snackCollectionId.toString(), + type = SnackSharedElementType.Image, + ), + ) + val restingRoundedPolygon = SnackPolygons.snackItemPolygon + val targetRoundedPolygon = SnackPolygons.pillIntermediatePolygon + val morph = remember { Morph(restingRoundedPolygon, targetRoundedPolygon) } + val progress = animatedVisibilityScope.transition.animateFloat(transitionSpec = { + tween(300, easing = LinearEasing) + }) { + when (it) { + EnterExitState.PreEnter -> 1f + EnterExitState.Visible -> 0f + EnterExitState.PostExit -> 1f + } + }.value + SnackImage( imageRes = snack.imageRes, contentDescription = null, style = { - shape(RoundedCornerShape(percent = 50)) + val shape = if (sharedContentState.isMatchFound) { + morph.asShape(progress) + } else { + restingRoundedPolygon.asShape() + } + shape(shape) }, modifier = Modifier - .size(width = 100.dp, height = 80.dp) - .sharedBounds( - rememberSharedContentState( - key = SnackSharedElementKey( - snackId = snack.id, - origin = snackCollectionId.toString(), - type = SnackSharedElementType.Image, - ), - ), + .size(width = 110.dp, height = 80.dp) + .sharedBoundsRevealWithShapeMorph( + sharedContentState = sharedContentState, + sharedTransitionScope = sharedTransitionScope, animatedVisibilityScope = animatedVisibilityScope, boundsTransform = snackDetailBoundsTransform, + targetShape = targetRoundedPolygon, + restingShape = restingRoundedPolygon ), ) Text( @@ -327,25 +358,45 @@ private fun HighlightSnackItem( }) .fillMaxSize(), ) { + val sharedContentState = rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = snackCollectionId.toString(), + type = SnackSharedElementType.Image, + ), + ) + val restingRoundedPolygon = SnackPolygons.snackDetailPolygon + val targetRoundedPolygon = SnackPolygons.pillIntermediatePolygon + val morph = remember { Morph(restingRoundedPolygon, targetRoundedPolygon) } + val progress = animatedVisibilityScope.transition.animateFloat(transitionSpec = { + tween(300, easing = LinearEasing) + }) { + when (it) { + EnterExitState.PreEnter -> 1f + EnterExitState.Visible -> 0f + EnterExitState.PostExit -> 1f + } + }.value + SnackImage( imageRes = snack.imageRes, contentDescription = null, style = { - shape(RoundedCornerShape(bottomEnd = 24.dp, bottomStart = 24.dp)) + val shape = if (sharedContentState.isMatchFound) { + morph.asShape(progress) + } else { + restingRoundedPolygon.asShape() + } + shape(shape) }, modifier = Modifier - .sharedBounds( - rememberSharedContentState( - key = SnackSharedElementKey( - snackId = snack.id, - origin = snackCollectionId.toString(), - type = SnackSharedElementType.Image, - ), - ), + .sharedBoundsRevealWithShapeMorph( + sharedContentState = sharedContentState, + sharedTransitionScope = sharedTransitionScope, animatedVisibilityScope = animatedVisibilityScope, - exit = fadeOut(nonSpatialExpressiveSpring()), - enter = fadeIn(nonSpatialExpressiveSpring()), boundsTransform = snackDetailBoundsTransform, + targetShape = targetRoundedPolygon, + restingShape = restingRoundedPolygon ) .fillMaxWidth() .size(120.dp), 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 ead453826e..8071ca4dac 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 @@ -14,7 +14,9 @@ * limitations under the License. */ -@file:OptIn(ExperimentalSharedTransitionApi::class, ExperimentalAnimationApi::class, ExperimentalMediaQueryApi::class) +@file:OptIn(ExperimentalSharedTransitionApi::class, ExperimentalAnimationApi::class, ExperimentalMediaQueryApi::class, + ExperimentalMaterial3ExpressiveApi::class +) package com.example.jetsnack.ui.snackdetail @@ -25,7 +27,9 @@ import androidx.compose.animation.EnterExitState import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateDp +import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn @@ -59,6 +63,7 @@ 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.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable @@ -72,6 +77,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.layout.Layout import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource @@ -82,10 +92,12 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.lerp import androidx.compose.ui.unit.sp import androidx.compose.ui.util.lerp +import androidx.graphics.shapes.Morph import com.example.jetsnack.R import com.example.jetsnack.model.Snack import com.example.jetsnack.model.SnackCollection @@ -105,9 +117,12 @@ 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.SnackPolygons import com.example.jetsnack.ui.utils.SharedElementPreviewWrapper import com.example.jetsnack.ui.utils.UiMediaScopeWrapper +import com.example.jetsnack.ui.utils.asShape import com.example.jetsnack.ui.utils.formatPrice +import com.example.jetsnack.ui.utils.sharedBoundsRevealWithShapeMorph import kotlin.math.max import kotlin.math.min @@ -442,29 +457,51 @@ private fun Image( ?: throw IllegalStateException("No animatedVisibilityScope found") with(sharedTransitionScope) { + val targetRoundedPolygon = SnackPolygons.snackDetailPolygon + val restingRoundedPolygon = SnackPolygons.pillIntermediatePolygon + val morph = remember { + Morph(restingRoundedPolygon, targetRoundedPolygon) + } + val progress = LocalNavAnimatedVisibilityScope.current?.transition?.animateFloat(transitionSpec = { + tween(300, easing = LinearEasing) + }) { + when (it) { + EnterExitState.PreEnter -> 1f + EnterExitState.Visible -> 0f + EnterExitState.PostExit -> 1f + } + }?.value ?: 0f + + val sharedContentState = rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snackId, + origin = origin, + type = SnackSharedElementType.Image, + ), + ) + SnackImage( imageRes = imageRes, contentDescription = null, style = { - shape(RoundedCornerShape(percent = 50)) + val shape = if (sharedContentState.isMatchFound) { + morph.asShape(progress) + } else { + restingRoundedPolygon.asShape() + } + shape(shape) }, modifier = Modifier .aspectRatio(1.3f) - .sharedBounds( - rememberSharedContentState( - key = SnackSharedElementKey( - snackId = snackId, - origin = origin, - type = SnackSharedElementType.Image, - ), - ), + .fillMaxSize() + .sharedBoundsRevealWithShapeMorph( + sharedContentState = sharedContentState, + sharedTransitionScope = sharedTransitionScope, animatedVisibilityScope = animatedVisibilityScope, - exit = fadeOut(), - enter = fadeIn(), boundsTransform = snackDetailBoundsTransform, + restingShape = restingRoundedPolygon, + targetShape = targetRoundedPolygon ) - .fillMaxSize(), - ) } } diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/SharedBoundsMorph.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/SharedBoundsMorph.kt new file mode 100644 index 0000000000..e3f07778ea --- /dev/null +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/SharedBoundsMorph.kt @@ -0,0 +1,138 @@ +package com.example.jetsnack.ui.utils + + +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.BoundsTransform +import androidx.compose.animation.EnterExitState +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.tween +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Matrix +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.asComposePath +import androidx.compose.foundation.shape.GenericShape +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection +import androidx.graphics.shapes.CornerRounding +import androidx.graphics.shapes.Morph +import androidx.graphics.shapes.RoundedPolygon +import androidx.graphics.shapes.circle +import androidx.graphics.shapes.pill +import androidx.graphics.shapes.rectangle +import androidx.graphics.shapes.toPath +import kotlin.math.max +import com.example.jetsnack.ui.LocalNavAnimatedVisibilityScope +import com.example.jetsnack.ui.LocalSharedTransitionScope + + +class MorphOverlayClip(val morph: Morph, private val animatedProgress: () -> Float) : + SharedTransitionScope.OverlayClip { + private val matrix = Matrix() + + override fun getClipPath( + sharedContentState: SharedTransitionScope.SharedContentState, + bounds: Rect, + layoutDirection: LayoutDirection, + density: Density, + ): Path? { + matrix.reset() + val max = max(bounds.width, bounds.height) + matrix.scale(max, max) + + val path = morph.toPath(progress = animatedProgress.invoke()).asComposePath() + path.transform(matrix) + path.translate(bounds.center + Offset(-max / 2f, -max / 2f)) + return path + } +} + + +@Composable +fun Modifier.sharedBoundsRevealWithShapeMorph( + sharedContentState: SharedTransitionScope.SharedContentState, + sharedTransitionScope: SharedTransitionScope? = LocalSharedTransitionScope.current, + animatedVisibilityScope: AnimatedVisibilityScope? = LocalNavAnimatedVisibilityScope.current, + boundsTransform: BoundsTransform = { tween(600)}, + resizeMode: SharedTransitionScope.ResizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds, + restingShape: RoundedPolygon = RoundedPolygon.rectangle().normalized(), + targetShape: RoundedPolygon = RoundedPolygon.circle().normalized(), + renderInOverlayDuringTransition: Boolean = true, + targetValueByState: @Composable (state: EnterExitState) -> Float = { + when (it) { + EnterExitState.PreEnter -> 1f + EnterExitState.Visible -> 0f + EnterExitState.PostExit -> 1f + } + }, + keepChildrenSizePlacement: Boolean = false, +): Modifier { + if (sharedTransitionScope == null || animatedVisibilityScope == null) return this + with(sharedTransitionScope) { + val animatedProgress = + animatedVisibilityScope.transition.animateFloat(targetValueByState = targetValueByState, + transitionSpec = { + tween(300, easing = LinearEasing) + } + ) + + val morph = remember { + Morph(restingShape, targetShape) + } + val morphClip = MorphOverlayClip(morph, { animatedProgress.value }) + val modifier = if (keepChildrenSizePlacement) { + Modifier + .skipToLookaheadSize() + .skipToLookaheadPosition() + } else { + Modifier + } + return this@sharedBoundsRevealWithShapeMorph + .sharedBounds( + sharedContentState = sharedContentState, + animatedVisibilityScope = animatedVisibilityScope, + boundsTransform = boundsTransform, + resizeMode = resizeMode, + clipInOverlayDuringTransition = morphClip, + renderInOverlayDuringTransition = renderInOverlayDuringTransition, + ) + .then(modifier) + } +} + +fun RoundedPolygon.asShape(): Shape = GenericShape { size: Size, _ -> + val matrix = Matrix().apply { scale(size.width, size.height) } + this.addPath(this@asShape.toPath().asComposePath()) + this.transform(matrix) +} + +fun Morph.asShape(progress: Float): Shape = GenericShape { size: Size, _ -> + val matrix = Matrix().apply { scale(size.width, size.height) } + this.addPath(this@asShape.toPath(progress).asComposePath()) + this.transform(matrix) +} + +object SnackPolygons { + val pillIntermediatePolygon = RoundedPolygon.pill(height = 1.85f).normalized() + + val snackItemPolygon = RoundedPolygon.rectangle( + perVertexRounding = listOf( + CornerRounding(0.5f), CornerRounding(0.5f), + CornerRounding(0.5f), CornerRounding(0.5f) + ) + ).normalized() + + val snackDetailPolygon = RoundedPolygon.rectangle( + perVertexRounding = listOf( + CornerRounding(0.25f), CornerRounding(0.25f), + CornerRounding(0.25f), CornerRounding(0.25f) + ) + ).normalized() +} diff --git a/Jetsnack/gradle/libs.versions.toml b/Jetsnack/gradle/libs.versions.toml index 888859e409..2a0f50ca4a 100644 --- a/Jetsnack/gradle/libs.versions.toml +++ b/Jetsnack/gradle/libs.versions.toml @@ -54,6 +54,7 @@ spotless = "8.2.1" targetSdk = "36" version-catalog-update = "1.1.0" uiTextGoogleFonts = "1.10.5" +graphicsShapes = "1.1.0" [libraries] accompanist-adaptive = { module = "com.google.accompanist:accompanist-adaptive", version.ref = "accompanist" } @@ -64,9 +65,9 @@ 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", version = "1.11.0-beta01" } +androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version = "1.12.0-SNAPSHOT" } androidx-compose-foundation-layout = { module = "androidx.compose.foundation:foundation-layout" } -androidx-compose-material3 = { module = "androidx.compose.material3:material3" } +androidx-compose-material3 = { module = "androidx.compose.material3:material3", version = "1.5.0-alpha15" } androidx-compose-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive" } androidx-compose-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout" } androidx-compose-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation" } @@ -153,6 +154,7 @@ roborazzi-rule = { module = "io.github.takahirom.roborazzi:roborazzi-junit-rule" rometools-modules = { module = "com.rometools:rome-modules", version.ref = "rome" } rometools-rome = { module = "com.rometools:rome", version.ref = "rome" } androidx-compose-ui-text-google-fonts = { group = "androidx.compose.ui", name = "ui-text-google-fonts", version.ref = "uiTextGoogleFonts" } +androidx-graphics-shapes = { group = "androidx.graphics", name = "graphics-shapes", version.ref = "graphicsShapes" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } diff --git a/Jetsnack/settings.gradle.kts b/Jetsnack/settings.gradle.kts index 3bc8533030..68987c4d84 100644 --- a/Jetsnack/settings.gradle.kts +++ b/Jetsnack/settings.gradle.kts @@ -13,28 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -val snapshotVersion : String? = System.getenv("COMPOSE_SNAPSHOT_ID") pluginManagement { repositories { - gradlePluginPortal() google() + gradlePluginPortal() mavenCentral() - maven { url = uri("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/org/jetbrains/kotlin/kotlin-compose-compiler-plugin/2.0.0-RC2-200/") } + + maven { + // You can find the maven URL for other artifacts (e.g. KMP, METALAVA) on their + // build pages. + url = uri("https://androidx.dev/snapshots/builds/15080370/artifacts/repository") + } } } + dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { - snapshotVersion?.let { - println("https://androidx.dev/snapshots/builds/$it/artifacts/repository/") - maven { url = uri("https://androidx.dev/snapshots/builds/$it/artifacts/repository/") } - maven { url = uri("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/org/jetbrains/kotlin/kotlin-compose-compiler-plugin/2.0.0-RC2-200/") } - } - google() mavenCentral() + maven { + // You can find the maven URL for other artifacts (e.g. KMP, METALAVA) on their + // build pages. + url = uri("https://androidx.dev/snapshots/builds/15080370/artifacts/repository") + } } } + rootProject.name = "Jetsnack" include(":app") From 969f85e69da62c996fe8dc6db93c385b583b6886 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 24 Mar 2026 20:21:46 +0000 Subject: [PATCH 17/33] Ktlint formatting and small tweaks --- .../example/jetsnack/ui/components/Button.kt | 13 ++---- .../example/jetsnack/ui/components/Filters.kt | 2 +- .../jetsnack/ui/components/Gradient.kt | 2 +- .../ui/components/GradientTintedIconButton.kt | 37 +++------------- .../example/jetsnack/ui/components/Snacks.kt | 10 ++--- .../example/jetsnack/ui/components/Surface.kt | 6 +-- .../jetsnack/ui/home/DestinationBar.kt | 2 +- .../java/com/example/jetsnack/ui/home/Feed.kt | 6 +-- .../example/jetsnack/ui/home/FilterScreen.kt | 43 ++++++++++++++----- .../java/com/example/jetsnack/ui/home/Home.kt | 2 +- .../com/example/jetsnack/ui/home/cart/Cart.kt | 11 +++-- .../jetsnack/ui/home/search/Categories.kt | 36 ++++++++-------- .../jetsnack/ui/home/search/Results.kt | 2 +- .../jetsnack/ui/snackdetail/SnackDetail.kt | 19 ++++---- .../com/example/jetsnack/ui/theme/Styles.kt | 24 ++++++----- .../jetsnack/ui/utils/PreviewWrapper.kt | 25 ++++++++--- .../jetsnack/ui/utils/SharedBoundsMorph.kt | 43 ++++++++++++------- 17 files changed, 154 insertions(+), 129 deletions(-) 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 ec5a3da765..67b6396b62 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 @@ -81,7 +81,7 @@ fun Button( @Preview("large font", "rectangle", fontScale = 2f) @Composable private fun ButtonPreview() { - UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { Button(onClick = {}) { Text(text = "Demo") } @@ -91,7 +91,7 @@ private fun ButtonPreview() { @Preview @Composable private fun ButtonPreviewLoading() { - UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { Button( onClick = {}, enabled = true, @@ -105,7 +105,7 @@ private fun ButtonPreviewLoading() { @Preview @Composable private fun ButtonPreviewDisabled() { - UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { Button( onClick = {}, enabled = false, @@ -132,7 +132,7 @@ private fun ButtonDesktopPreviewDisabled() { UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Physical, UiMediaScope.PointerPrecision.Fine) { Button( onClick = {}, - enabled = false + enabled = false, ) { Text(text = "Demo") } @@ -155,8 +155,3 @@ private fun RectangleButtonPreview() { } } } - - - - - 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 5c7d6c5227..704a628a26 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 @@ -91,7 +91,7 @@ fun FilterBar( minHeight(32.dp) border(3.dp, Brush.linearGradient(colors.interactiveSecondary)) shape(RoundedCornerShape(50)) - } + }, ) } } 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 a27f8c0cdb..ff061e82ce 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 @@ -35,4 +35,4 @@ fun Modifier.contentTintDiagonalGradient(colors: List, blendMode: BlendMo brush = Brush.linearGradient(colors), blendMode = blendMode, ) -} \ No newline at end of file +} 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 5b958a60f4..00dd18b548 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 @@ -22,26 +22,21 @@ import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.padding import androidx.compose.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.foundation.style.Style 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.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.BlendMode -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview 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, @@ -49,44 +44,24 @@ fun JetsnackGradientTintedIconButton( contentDescription: String?, modifier: Modifier = Modifier, style: Style = Style, - // TODO we cannot migrate this out yet!! - colors: List = JetsnackTheme.colors.interactiveSecondary, ) { val interactionSource = remember { MutableInteractionSource() } val styleState = rememberUpdatedStyleState(interactionSource) Surface( + style = JetsnackTheme.styles.gradientIconButtonStyle then style, + styleState = styleState, modifier = modifier .clickable( onClick = onClick, interactionSource = interactionSource, indication = null, - ) - .styleable(styleState, JetsnackTheme.styles.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, - modifier = modifierColor, + tint = JetsnackTheme.colors.textPrimary, ) } } @@ -103,4 +78,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/Snacks.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Snacks.kt index 428e6ed1dc..9d83f2f336 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 @@ -209,7 +209,7 @@ fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String start = 4.dp, end = 4.dp, bottom = 8.dp, - ) + ), ) { val sharedTransitionScope = LocalSharedTransitionScope.current ?: throw IllegalStateException("No sharedTransitionScope found") @@ -264,7 +264,7 @@ fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String animatedVisibilityScope = animatedVisibilityScope, boundsTransform = snackDetailBoundsTransform, targetShape = targetRoundedPolygon, - restingShape = restingRoundedPolygon + restingShape = restingRoundedPolygon, ), ) Text( @@ -346,7 +346,7 @@ private fun HighlightSnackItem( ), enter = fadeIn(), exit = fadeOut(), - ) + ), ) { Column( modifier = Modifier @@ -396,7 +396,7 @@ private fun HighlightSnackItem( animatedVisibilityScope = animatedVisibilityScope, boundsTransform = snackDetailBoundsTransform, targetShape = targetRoundedPolygon, - restingShape = restingRoundedPolygon + restingShape = restingRoundedPolygon, ) .fillMaxWidth() .size(120.dp), @@ -474,7 +474,7 @@ fun SnackImage( imageRes: Int, contentDescription: String?, modifier: Modifier = Modifier, - style: Style = Style + style: Style = Style, ) { Surface( style = Style { 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 6f24605445..44549321d2 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 @@ -37,14 +37,14 @@ fun Surface( modifier: Modifier = Modifier, style: Style = Style, // todo confirm patten is acceptable - styleState: StyleState = rememberUpdatedStyleState(null), + styleState: StyleState = rememberUpdatedStyleState(null), content: @Composable () -> Unit, ) { Box( modifier = modifier - .styleable(styleState, JetsnackTheme.styles.surfaceStyle, style) + .styleable(styleState, JetsnackTheme.styles.surfaceStyle, style), ) { - //todo double check CompositionLocalProvider(LocalContentColor provides contentColor, content = content) + // todo double check CompositionLocalProvider(LocalContentColor provides contentColor, content = content) content() } } 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 e9dd3f7023..63d5c3f8d8 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 @@ -105,7 +105,7 @@ fun DestinationBar(modifier: Modifier = Modifier) { .copy(alpha = AlphaNearOpaque), titleContentColor = JetsnackTheme.colors.textSecondary, ), - modifier = Modifier.height(48.dp) + modifier = Modifier.height(48.dp), ) JetsnackDivider() } 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 415c9fc651..25b19e0e07 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,8 +47,8 @@ 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.Surface import com.example.jetsnack.ui.components.SnackCollection +import com.example.jetsnack.ui.components.Surface import com.example.jetsnack.ui.theme.JetsnackTheme @Composable @@ -112,7 +112,7 @@ private fun SnackCollectionList( item { Spacer( Modifier.windowInsetsTopHeight( - WindowInsets.systemBars + WindowInsets.systemBars, ), ) FilterBar( @@ -120,7 +120,7 @@ private fun SnackCollectionList( sharedTransitionScope = sharedTransitionScope, filterScreenVisible = filtersVisible, onShowFilters = onFiltersSelected, - modifier = Modifier.padding(top = 48.dp) + modifier = Modifier.padding(top = 48.dp), ) } itemsIndexed(snackCollections) { index, 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 0bf868481c..efe286b9ad 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 @@ -14,10 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalLayoutApi::class, ExperimentalSharedTransitionApi::class) +@file:OptIn(ExperimentalLayoutApi::class, ExperimentalSharedTransitionApi::class, ExperimentalMaterial3Api::class) package com.example.jetsnack.ui.home +import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.annotation.DrawableRes import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope @@ -43,11 +44,15 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.style.Style +import androidx.compose.foundation.style.styleable import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider +import androidx.compose.material3.SliderColors import androidx.compose.material3.SliderDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -58,6 +63,7 @@ import androidx.compose.runtime.setValue 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.res.painterResource import androidx.compose.ui.res.stringResource @@ -134,6 +140,7 @@ fun FilterScreen(sharedTransitionScope: SharedTransitionScope, animatedVisibilit Icon( painter = painterResource(id = R.drawable.ic_close), contentDescription = stringResource(id = R.string.close), + tint = JetsnackTheme.colors.textInteractive, ) } Text( @@ -256,25 +263,35 @@ fun MaxCalories(sliderPosition: Float, onValueChanged: (Float) -> Unit) { text = stringResource(id = R.string.per_serving), style = { textStyleWithFontFamilyFix(typography.bodyMedium) - contentColor(colors.brand) + contentColor(colors.textSecondary) }, modifier = Modifier.padding(top = 5.dp, start = 10.dp), ) } + val interactionSource = remember { MutableInteractionSource() } Slider( value = sliderPosition, onValueChange = { newValue -> onValueChanged(newValue) }, + thumb = { + SliderDefaults.Thumb( + interactionSource = interactionSource, + colors = SliderDefaults.colors(thumbColor = JetsnackTheme.colors.brand), + enabled = true, + ) + }, + track = { + Box( + modifier = Modifier.fillMaxWidth().height(4.dp).styleable(null) { + background(Brush.horizontalGradient(colors.gradient1)) + }, + ) + }, valueRange = 0f..300f, steps = 5, modifier = Modifier .fillMaxWidth(), - colors = SliderDefaults.colors( - thumbColor = JetsnackTheme.colors.brand, - activeTrackColor = JetsnackTheme.colors.brand, - inactiveTrackColor = JetsnackTheme.colors.iconInteractive, - ), ) } @@ -284,7 +301,7 @@ fun FilterTitle(text: String) { text = text, style = { textStyleWithFontFamilyFix(typography.titleLarge) - contentColor(colors.brand) + contentColor(colors.textPrimary) }, modifier = Modifier.padding(bottom = 8.dp), ) @@ -298,11 +315,16 @@ fun SortOption(text: String, @DrawableRes icon: Int?, onClickOption: () -> Unit, .selectable(selected) { onClickOption() }, ) { if (icon != null) { - Icon(painter = painterResource(id = icon), contentDescription = null) + Icon( + painter = painterResource(id = icon), + contentDescription = null, + tint = JetsnackTheme.colors.textInteractive, + ) } Text( text = text, style = { + contentColor(colors.textInteractive) textStyleWithFontFamilyFix(typography.titleMedium) }, modifier = Modifier @@ -313,13 +335,14 @@ fun SortOption(text: String, @DrawableRes icon: Int?, onClickOption: () -> Unit, Icon( painter = painterResource(id = R.drawable.ic_check), contentDescription = null, - tint = JetsnackTheme.colors.brand, + tint = JetsnackTheme.colors.textInteractive, ) } } } @Preview("filter screen") +@Preview(uiMode = UI_MODE_NIGHT_YES) @Composable fun FilterScreenPreview() { JetsnackTheme { 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 39807ece31..2b539dff93 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 @@ -415,7 +415,7 @@ private fun JetsnackBottomNavIndicator() { shape(BottomNavIndicatorShape) border(3.dp, Brush.linearGradient(colors.interactiveMask)) externalPadding(horizontal = 16.dp, vertical = 8.dp) - } + }, ) } 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 d540e6b2bb..cb2e2c5e5f 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 @@ -84,10 +84,10 @@ import com.example.jetsnack.ui.LocalNavAnimatedVisibilityScope import com.example.jetsnack.ui.LocalSharedTransitionScope import com.example.jetsnack.ui.components.Button import com.example.jetsnack.ui.components.JetsnackDivider -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.Text import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix import com.example.jetsnack.ui.home.DestinationBar import com.example.jetsnack.ui.snackdetail.nonSpatialExpressiveSpring @@ -332,7 +332,7 @@ fun CartItem( shape(shapes.small) externalPaddingVertical(8.dp) size(100.dp) - } + }, ) Column( modifier = Modifier @@ -351,7 +351,7 @@ fun CartItem( ) IconButton( onClick = { removeSnack(snack.id) }, - modifier = Modifier.offset(x = 12.dp) + modifier = Modifier.offset(x = 12.dp), ) { Icon( painter = painterResource(id = R.drawable.ic_close), @@ -387,12 +387,12 @@ fun CartItem( count = orderLine.count, decreaseItemCount = { decreaseItemCount(snack.id) }, increaseItemCount = { increaseItemCount(snack.id) }, - modifier = Modifier.alignBy(LastBaseline) + modifier = Modifier.alignBy(LastBaseline), ) } } } - JetsnackDivider(/*modifier = Modifier.padding(top = 8.dp)*/) + JetsnackDivider() } } @@ -529,4 +529,3 @@ private fun CartPreview() { } } } - 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 e2b0f9d9fd..58e14ba8a7 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 @@ -53,8 +53,8 @@ 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.Text import com.example.jetsnack.ui.components.VerticalGrid import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix import com.example.jetsnack.ui.theme.JetsnackTheme @@ -94,10 +94,10 @@ private fun SearchCategoryCollection(collection: SearchCategoryCollection, index collection.categories.forEach { category -> SearchCategory( category = category, - modifier = Modifier.padding( 4.dp), + modifier = Modifier.padding(4.dp), style = { border(1.dp, borderColor) - } + }, ) } } @@ -110,24 +110,26 @@ private val CategoryShape = RoundedCornerShape(24.dp) private const val CategoryTextProportion = 0.55f @Composable -private fun SearchCategory(category: SearchCategory, - modifier: Modifier = Modifier, - style: Style = Style) { +private fun SearchCategory(category: SearchCategory, modifier: Modifier = Modifier, style: Style = Style) { val interactionSource = remember { MutableInteractionSource() } val styleState = rememberUpdatedStyleState(interactionSource) Layout( modifier = modifier .aspectRatio(1.85f) - .styleable(styleState, Style { - shape(CategoryShape) - clip(true) - background(colors.uiBackground) - dropShadow(Shadow(color = Color(0xffE5E1E2), radius = 8.dp)) - textStyleWithFontFamilyFix(typography.titleMedium) - contentColor(Color(0xff005C5E)) - } then style) - .clickable(interactionSource = interactionSource, - indication = ripple() + .styleable( + styleState, + Style { + shape(CategoryShape) + clip(true) + background(colors.uiBackground) + dropShadow(Shadow(color = Color(0xffE5E1E2), radius = 8.dp)) + textStyleWithFontFamilyFix(typography.titleSmall) + contentColor(colors.textPrimary) + } then style, + ) + .clickable( + interactionSource = interactionSource, + indication = ripple(), ) { /* todo */ }, content = { Text( @@ -184,7 +186,7 @@ private fun SearchCategoryPreview() { ), style = { border(1.dp, Color(0xFF8BDEBE)) - } + }, ) } } 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 14057014f9..8a7b3217a0 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 @@ -46,9 +46,9 @@ import com.example.jetsnack.model.Snack import com.example.jetsnack.model.snacks import com.example.jetsnack.ui.components.Button import com.example.jetsnack.ui.components.JetsnackDivider +import com.example.jetsnack.ui.components.SnackImage 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 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 8071ca4dac..b5b2d38cdf 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 @@ -14,8 +14,9 @@ * limitations under the License. */ -@file:OptIn(ExperimentalSharedTransitionApi::class, ExperimentalAnimationApi::class, ExperimentalMediaQueryApi::class, - ExperimentalMaterial3ExpressiveApi::class +@file:OptIn( + ExperimentalSharedTransitionApi::class, ExperimentalAnimationApi::class, ExperimentalMediaQueryApi::class, + ExperimentalMaterial3ExpressiveApi::class, ) package com.example.jetsnack.ui.snackdetail @@ -108,17 +109,17 @@ import com.example.jetsnack.ui.SnackSharedElementKey import com.example.jetsnack.ui.SnackSharedElementType import com.example.jetsnack.ui.components.Button import com.example.jetsnack.ui.components.JetsnackDivider -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.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 -import com.example.jetsnack.ui.utils.SnackPolygons import com.example.jetsnack.ui.utils.SharedElementPreviewWrapper +import com.example.jetsnack.ui.utils.SnackPolygons import com.example.jetsnack.ui.utils.UiMediaScopeWrapper import com.example.jetsnack.ui.utils.asShape import com.example.jetsnack.ui.utils.formatPrice @@ -500,8 +501,8 @@ private fun Image( animatedVisibilityScope = animatedVisibilityScope, boundsTransform = snackDetailBoundsTransform, restingShape = restingRoundedPolygon, - targetShape = targetRoundedPolygon - ) + targetShape = targetRoundedPolygon, + ), ) } } @@ -580,7 +581,7 @@ private fun CartBottomBar(modifier: Modifier = Modifier) { modifier = Modifier.weight(1f), style = { externalPadding(4.dp) - } + }, ) { Text( text = stringResource(R.string.add_to_cart), @@ -603,7 +604,7 @@ private fun CartBottomBar(modifier: Modifier = Modifier) { @Preview("large font", fontScale = 2f) @Composable private fun SnackDetailPreview() { - JetsnackTheme{ + JetsnackTheme { SharedElementPreviewWrapper { UiMediaScopeWrapper { SnackDetail( 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 index 5f7da6bd60..45ceb187ac 100644 --- 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 @@ -60,9 +60,13 @@ fun StyleScope.adaptiveFontSize(fontSize: TextUnit) { } scaleFactor = when (LocalUiMediaScope.currentValue.pointerPrecision) { UiMediaScope.PointerPrecision.Coarse -> scaleFactor * 1f + UiMediaScope.PointerPrecision.Blunt -> scaleFactor * 0.66f + UiMediaScope.PointerPrecision.Fine -> scaleFactor * 1f + UiMediaScope.PointerPrecision.None -> scaleFactor + else -> { scaleFactor } @@ -120,9 +124,9 @@ data class Styles( ellipticalGradient( colors = colors.interactivePrimary, radiusXPercent = 1.3f, - radiusYPercent = 0.7232f, + radiusYPercent = 0.7f, centerXPercent = 0.4f, - centerYPercent = 0.55f, + centerYPercent = 0.5f, ), ) @@ -155,9 +159,9 @@ data class Styles( ellipticalGradient( colors = colors.interactivePrimary.reversed(), radiusXPercent = 1.3f, - radiusYPercent = 0.7232f, + radiusYPercent = 0.7f, centerXPercent = 0.4f, - centerYPercent = 0.55f, + centerYPercent = 0.5f, ), ) dropShadow(Shadow(color = colors.brand, offset = DpOffset(x = 0.dp, y = 0.dp), radius = 0.dp)) @@ -178,9 +182,6 @@ data class Styles( 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)) @@ -192,11 +193,11 @@ data class Styles( clip(true) border(2.dp, Brush.linearGradient(colors.interactiveSecondary)) background(colors.uiBackground) + contentColor(colors.textPrimary) 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, @@ -231,12 +232,13 @@ data class Styles( background(colors.brand) contentColor(colors.textSecondary) border(2.dp, colors.brand) - dropShadow(Shadow(color = colors.brand, radius = 6.dp, offset = DpOffset(0.dp, 2.dp) )) + dropShadow(Shadow(color = colors.brand, radius = 6.dp, offset = DpOffset(0.dp, 2.dp))) innerShadow(Shadow(color = colors.brand, offset = DpOffset((-6).dp, (-8).dp), radius = 8.dp)) } } }, val defaultTextStyle: Style = Style { + contentColor(colors.textPrimary) textStyleWithFontFamilyFix(LocalTextStyle.currentValue) }, val surfaceStyle: Style = Style { @@ -250,7 +252,7 @@ data class Styles( enum class LoadingState { Loading, Loaded, - Error + Error, } val loadingStateKey = StyleStateKey(LoadingState.Loaded) @@ -272,4 +274,4 @@ fun StyleScope.loaded(value: Style) { fun StyleScope.error(value: Style) { state(loadingStateKey, value, { key, state -> state[key] == LoadingState.Error }) -} \ No newline at end of file +} diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt index a660a74bf0..c6b1cb03e1 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt @@ -1,5 +1,21 @@ +/* + * 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(ExperimentalMediaQueryApi::class, ExperimentalComposeUiApi::class) + package com.example.jetsnack.ui.utils import androidx.compose.animation.AnimatedVisibility @@ -39,7 +55,8 @@ fun SharedElementPreviewWrapper(content: @Composable () -> Unit) { fun UiMediaScopeWrapper( keyboardKind: UiMediaScope.KeyboardKind = UiMediaScope.KeyboardKind.Virtual, pointerPrecision: UiMediaScope.PointerPrecision = UiMediaScope.PointerPrecision.Blunt, - content: @Composable () -> Unit) { + content: @Composable () -> Unit, +) { ComposeUiFlags.isMediaQueryIntegrationEnabled = true BoxWithConstraints { @@ -61,11 +78,9 @@ fun UiMediaScopeWrapper( content() } } - } } - // Assuming KeyboardKind is your enum/class @OptIn(ExperimentalMediaQueryApi::class) class KeyboardKindProvider : PreviewParameterProvider { @@ -80,6 +95,6 @@ class PointerPrecisionProvider : PreviewParameterProvider Float) : - SharedTransitionScope.OverlayClip { +class MorphOverlayClip(val morph: Morph, private val animatedProgress: () -> Float) : SharedTransitionScope.OverlayClip { private val matrix = Matrix() override fun getClipPath( @@ -54,13 +67,12 @@ class MorphOverlayClip(val morph: Morph, private val animatedProgress: () -> Flo } } - @Composable fun Modifier.sharedBoundsRevealWithShapeMorph( sharedContentState: SharedTransitionScope.SharedContentState, sharedTransitionScope: SharedTransitionScope? = LocalSharedTransitionScope.current, animatedVisibilityScope: AnimatedVisibilityScope? = LocalNavAnimatedVisibilityScope.current, - boundsTransform: BoundsTransform = { tween(600)}, + boundsTransform: BoundsTransform = { tween(600) }, resizeMode: SharedTransitionScope.ResizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds, restingShape: RoundedPolygon = RoundedPolygon.rectangle().normalized(), targetShape: RoundedPolygon = RoundedPolygon.circle().normalized(), @@ -77,11 +89,12 @@ fun Modifier.sharedBoundsRevealWithShapeMorph( if (sharedTransitionScope == null || animatedVisibilityScope == null) return this with(sharedTransitionScope) { val animatedProgress = - animatedVisibilityScope.transition.animateFloat(targetValueByState = targetValueByState, + animatedVisibilityScope.transition.animateFloat( + targetValueByState = targetValueByState, transitionSpec = { tween(300, easing = LinearEasing) - } - ) + }, + ) val morph = remember { Morph(restingShape, targetShape) @@ -125,14 +138,14 @@ object SnackPolygons { val snackItemPolygon = RoundedPolygon.rectangle( perVertexRounding = listOf( CornerRounding(0.5f), CornerRounding(0.5f), - CornerRounding(0.5f), CornerRounding(0.5f) - ) + CornerRounding(0.5f), CornerRounding(0.5f), + ), ).normalized() val snackDetailPolygon = RoundedPolygon.rectangle( perVertexRounding = listOf( CornerRounding(0.25f), CornerRounding(0.25f), - CornerRounding(0.25f), CornerRounding(0.25f) - ) + CornerRounding(0.25f), CornerRounding(0.25f), + ), ).normalized() } From b99448a0afbd9cbe02f820107fa02f355b885168 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Wed, 25 Mar 2026 14:26:59 +0000 Subject: [PATCH 18/33] Update Button styles and improve UI layout for interactive states This change updates the design system's button styles to better handle various interaction states and refactors several components to improve layout consistency and edge-to-edge support. ### Summary of changes 1. **Style Updates**: * Added explicit `hovered` and `focused` state definitions with animations in `Styles.kt`. * Introduced `minHeight` constraints for buttons, with specific values for physical keyboard vs. touch-optimized layouts. * Refined `dropShadow` and `innerShadow` properties for buttons. * Updated the default button shape from `RectangleShape` to `RoundedCornerShape(4.dp)` in preview and cart contexts. 2. **Component Refactoring**: * **Snacks**: Simplified `HighlightedSnacks` and `HighlightSnackItem` by removing unused parameters including `index`, `gradient`, and `scrollProvider`. * **SnackDetail**: Centered the "See More" button and updated the bottom bar to use `widthIn` constraints instead of proportional weights. * **Cart**: Updated the summary layout to use `systemBars` insets and constrained the checkout button width. 3. **Edge-to-Edge Support**: * Migrated from `statusBars` to `systemBars` insets in `Cart.kt` and `SnackDetail.kt` to ensure proper padding across different system configurations. 4. **Enhanced Previews**: * Added comprehensive `@Preview` functions in `Button.kt` to visualize interactive state combinations, including Pressed, Hovered, Focused, and their overlapping states. --- .../com/example/jetsnack/ui/MainActivity.kt | 2 +- .../example/jetsnack/ui/components/Button.kt | 127 +++++++++++++++++- .../example/jetsnack/ui/components/Snacks.kt | 71 ++++------ .../com/example/jetsnack/ui/home/cart/Cart.kt | 27 ++-- .../jetsnack/ui/snackdetail/SnackDetail.kt | 35 ++--- .../com/example/jetsnack/ui/theme/Styles.kt | 43 +++++- 6 files changed, 215 insertions(+), 90 deletions(-) diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/MainActivity.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/MainActivity.kt index a6622c1af5..b6cb1592ee 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/MainActivity.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/MainActivity.kt @@ -52,7 +52,7 @@ class MainActivity : ComponentActivity() { val installedProviders = getSystemService(AppWidgetManager::class.java).installedProviders val providerInfo = installedProviders.firstOrNull { it.provider.className == - receiver.qualifiedName + receiver.qualifiedName } providerInfo?.generatedPreviewCategories.takeIf { it == 0 }?.let { // Set previews if this provider if unset 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 67b6396b62..6569c249b6 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 @@ -20,24 +20,30 @@ package com.example.jetsnack.ui.components import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.FocusInteraction +import androidx.compose.foundation.interaction.HoverInteraction import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope +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.styleable import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.Modifier import androidx.compose.ui.UiMediaScope -import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.theme.LoadingState import com.example.jetsnack.ui.theme.loadingState @@ -78,7 +84,6 @@ fun Button( @Preview @Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) -@Preview("large font", "rectangle", fontScale = 2f) @Composable private fun ButtonPreview() { UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { @@ -89,6 +94,7 @@ private fun ButtonPreview() { } @Preview +@Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) @Composable private fun ButtonPreviewLoading() { UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { @@ -103,6 +109,7 @@ private fun ButtonPreviewLoading() { } @Preview +@Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) @Composable private fun ButtonPreviewDisabled() { UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { @@ -115,6 +122,119 @@ private fun ButtonPreviewDisabled() { } } +@Preview +@Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) +@Composable +private fun ButtonPreviewPressed() { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { + val interactionSource = remember { MutableInteractionSource() } + LaunchedEffect(interactionSource) { + interactionSource.emit(PressInteraction.Press(Offset.Zero)) + } + Button( + onClick = {}, + interactionSource = interactionSource, + ) { + Text(text = "Demo") + } + } +} + +@Preview +@Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) +@Composable +private fun ButtonPreviewHovered() { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { + val interactionSource = remember { MutableInteractionSource() } + LaunchedEffect(interactionSource) { + interactionSource.emit(HoverInteraction.Enter()) + } + Button( + onClick = {}, + interactionSource = interactionSource, + ) { + Text(text = "Demo") + } + } +} + +@Preview +@Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) +@Composable +private fun ButtonPreviewFocused() { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { + val interactionSource = remember { MutableInteractionSource() } + LaunchedEffect(interactionSource) { + interactionSource.emit(FocusInteraction.Focus()) + } + Button( + onClick = {}, + interactionSource = interactionSource, + ) { + Text(text = "Demo") + } + } +} + +@Preview +@Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) +@Composable +private fun ButtonPreviewHoveredFocused() { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { + val interactionSource = remember { MutableInteractionSource() } + LaunchedEffect(interactionSource) { + interactionSource.emit(HoverInteraction.Enter()) + interactionSource.emit(FocusInteraction.Focus()) + } + Button( + onClick = {}, + interactionSource = interactionSource, + ) { + Text(text = "Demo") + } + } +} + +@Preview +@Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) +@Composable +private fun ButtonPreviewPressedFocused() { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { + val interactionSource = remember { MutableInteractionSource() } + LaunchedEffect(interactionSource) { + interactionSource.emit(FocusInteraction.Focus()) + interactionSource.emit(PressInteraction.Press(Offset.Zero)) + } + Button( + onClick = {}, + interactionSource = interactionSource, + ) { + Text(text = "Demo") + } + } +} + +// TODO this state is broken visually +@Preview +@Preview("dark theme", "rectangle", uiMode = UI_MODE_NIGHT_YES) +@Composable +private fun ButtonPreviewPressedHovered() { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, UiMediaScope.PointerPrecision.Blunt) { + val interactionSource = remember { MutableInteractionSource() } + LaunchedEffect(interactionSource) { + interactionSource.emit(PressInteraction.Press(Offset.Zero)) + interactionSource.emit(HoverInteraction.Enter()) + } + Button( + onClick = {}, + interactionSource = interactionSource, + ) { + Text(text = "Demo") + } + } +} + + @Preview @Composable private fun ButtonDesktopPreview() { @@ -126,6 +246,7 @@ private fun ButtonDesktopPreview() { } } } + @Preview @Composable private fun ButtonDesktopPreviewDisabled() { @@ -148,7 +269,7 @@ private fun RectangleButtonPreview() { Button( onClick = {}, style = { - shape(RectangleShape) + shape(RoundedCornerShape(4.dp)) }, ) { Text(text = "Demo") 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 9d83f2f336..0724a8ce96 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 @@ -62,15 +62,12 @@ 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.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp import androidx.graphics.shapes.Morph import coil.compose.AsyncImage @@ -96,9 +93,6 @@ import com.example.jetsnack.ui.utils.formatPrice import com.example.jetsnack.ui.utils.sharedBoundsRevealWithShapeMorph private val HighlightCardWidth = 170.dp -private val HighlightCardPadding = 16.dp -private val Density.cardWidthWithPaddingPx - get() = (HighlightCardWidth + HighlightCardPadding).toPx() @Composable fun SnackCollection( @@ -139,7 +133,7 @@ fun SnackCollection( } } if (highlight && snackCollection.type == CollectionType.Highlight) { - HighlightedSnacks(snackCollection.id, index, snackCollection.snacks, onSnackClick) + HighlightedSnacks(snackCollection.id, snackCollection.snacks, onSnackClick) } else { Snacks(snackCollection.id, snackCollection.snacks, onSnackClick) } @@ -149,39 +143,22 @@ fun SnackCollection( @Composable private fun HighlightedSnacks( snackCollectionId: Long, - index: Int, snacks: List, onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, ) { val rowState = rememberLazyListState() - val cardWidthWithPaddingPx = with(LocalDensity.current) { cardWidthWithPaddingPx } - - val scrollProvider = { - // Simple calculation of scroll distance for homogenous item types with the same width. - val offsetFromStart = cardWidthWithPaddingPx * rowState.firstVisibleItemIndex - offsetFromStart + rowState.firstVisibleItemScrollOffset - } - - val gradient = when ((index / 2) % 2) { - 0 -> JetsnackTheme.colors.gradient1 - else -> JetsnackTheme.colors.gradient2 - } - LazyRow( state = rowState, modifier = modifier, horizontalArrangement = Arrangement.spacedBy(16.dp), contentPadding = PaddingValues(start = 24.dp, end = 24.dp), ) { - itemsIndexed(snacks) { index, snack -> + itemsIndexed(snacks) { _, snack -> HighlightSnackItem( snackCollectionId = snackCollectionId, snack = snack, onSnackClick = onSnackClick, - index = index, - gradient = gradient, - scrollProvider = scrollProvider, ) } } @@ -220,9 +197,11 @@ fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier - .clickable(onClick = { - onSnackClick(snack.id, snackCollectionId.toString()) - }) + .clickable( + onClick = { + onSnackClick(snack.id, snackCollectionId.toString()) + }, + ) .padding(8.dp), ) { val sharedContentState = rememberSharedContentState( @@ -235,9 +214,11 @@ fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String val restingRoundedPolygon = SnackPolygons.snackItemPolygon val targetRoundedPolygon = SnackPolygons.pillIntermediatePolygon val morph = remember { Morph(restingRoundedPolygon, targetRoundedPolygon) } - val progress = animatedVisibilityScope.transition.animateFloat(transitionSpec = { - tween(300, easing = LinearEasing) - }) { + val progress = animatedVisibilityScope.transition.animateFloat( + transitionSpec = { + tween(300, easing = LinearEasing) + }, + ) { when (it) { EnterExitState.PreEnter -> 1f EnterExitState.Visible -> 0f @@ -301,9 +282,6 @@ private fun HighlightSnackItem( snackCollectionId: Long, snack: Snack, onSnackClick: (Long, String) -> Unit, - index: Int, - gradient: List, - scrollProvider: () -> Float, modifier: Modifier = Modifier, ) { val sharedTransitionScope = LocalSharedTransitionScope.current @@ -350,12 +328,14 @@ private fun HighlightSnackItem( ) { Column( modifier = Modifier - .clickable(onClick = { - onSnackClick( - snack.id, - snackCollectionId.toString(), - ) - }) + .clickable( + onClick = { + onSnackClick( + snack.id, + snackCollectionId.toString(), + ) + }, + ) .fillMaxSize(), ) { val sharedContentState = rememberSharedContentState( @@ -368,9 +348,11 @@ private fun HighlightSnackItem( val restingRoundedPolygon = SnackPolygons.snackDetailPolygon val targetRoundedPolygon = SnackPolygons.pillIntermediatePolygon val morph = remember { Morph(restingRoundedPolygon, targetRoundedPolygon) } - val progress = animatedVisibilityScope.transition.animateFloat(transitionSpec = { - tween(300, easing = LinearEasing) - }) { + val progress = animatedVisibilityScope.transition.animateFloat( + transitionSpec = { + tween(300, easing = LinearEasing) + }, + ) { when (it) { EnterExitState.PreEnter -> 1f EnterExitState.Visible -> 0f @@ -506,9 +488,6 @@ fun SnackCardPreview() { snackCollectionId = 1, snack = snack, onSnackClick = { _, _ -> }, - index = 0, - gradient = JetsnackTheme.colors.gradient1, - scrollProvider = { 0f }, ) } } 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 cb2e2c5e5f..f9e505f531 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 @@ -19,23 +19,17 @@ package com.example.jetsnack.ui.home.cart import android.content.res.Configuration.UI_MODE_NIGHT_YES -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.SharedTransitionLayout -import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.add -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -43,7 +37,8 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.windowInsetsTopHeight import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth @@ -54,17 +49,13 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Surface import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.Modifier -import androidx.compose.ui.UiMediaScope.KeyboardKind -import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.LastBaseline -import androidx.compose.ui.layout.LookaheadScope import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -80,8 +71,6 @@ 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.LocalNavAnimatedVisibilityScope -import com.example.jetsnack.ui.LocalSharedTransitionScope import com.example.jetsnack.ui.components.Button import com.example.jetsnack.ui.components.JetsnackDivider import com.example.jetsnack.ui.components.QuantitySelector @@ -131,7 +120,9 @@ fun Cart( onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, ) { - com.example.jetsnack.ui.components.Surface(modifier = modifier.fillMaxSize()) { + com.example.jetsnack.ui.components.Surface( + modifier = modifier.fillMaxSize(), + ) { Box(modifier = Modifier.fillMaxSize()) { CartContent( orderLines = orderLines, @@ -171,7 +162,7 @@ private fun CartContent( item(key = "title") { Spacer( Modifier.windowInsetsTopHeight( - WindowInsets.statusBars.add(WindowInsets(top = 56.dp)), + WindowInsets.systemBars.add(WindowInsets(top = 56.dp)), ), ) Text( @@ -490,11 +481,11 @@ private fun CheckoutBar(modifier: Modifier = Modifier) { Button( onClick = { /* todo */ }, style = { - shape(RectangleShape) + shape(RoundedCornerShape(4.dp)) }, modifier = Modifier - .padding(horizontal = 12.dp, vertical = 8.dp) - .weight(1f), + .widthIn(max = 200.dp) + .padding(horizontal = 12.dp, vertical = 8.dp), ) { Text( text = stringResource(id = R.string.cart_checkout), 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 b5b2d38cdf..56431d47ff 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 @@ -47,6 +47,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -57,12 +58,14 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.wrapContentSize 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.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon @@ -78,11 +81,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.asComposePath import androidx.compose.ui.layout.Layout import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource @@ -93,7 +91,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.lerp import androidx.compose.ui.unit.sp @@ -172,6 +169,7 @@ fun SnackDetail(snackId: Long, origin: String, upPress: () -> Unit) { Box( Modifier .clip(RoundedCornerShape(roundedCornerAnim)) + .windowInsetsPadding(WindowInsets.systemBars) .sharedBounds( rememberSharedContentState( key = SnackSharedElementKey( @@ -290,14 +288,15 @@ private fun Body(related: List, scroll: ScrollState) { textStyleWithFontFamilyFix(typography.labelLarge) contentColor(colors.textLink) textAlign(TextAlign.Center) - contentPaddingTop(15.dp) - fillWidth() + contentPadding(16.dp) }, modifier = Modifier .heightIn(20.dp) .clickable { seeMore = !seeMore } + .align(Alignment.CenterHorizontally) + .wrapContentSize() .skipToLookaheadSize(), ) @@ -463,9 +462,11 @@ private fun Image( val morph = remember { Morph(restingRoundedPolygon, targetRoundedPolygon) } - val progress = LocalNavAnimatedVisibilityScope.current?.transition?.animateFloat(transitionSpec = { - tween(300, easing = LinearEasing) - }) { + val progress = LocalNavAnimatedVisibilityScope.current?.transition?.animateFloat( + transitionSpec = { + tween(300, easing = LinearEasing) + }, + ) { when (it) { EnterExitState.PreEnter -> 1f EnterExitState.Visible -> 0f @@ -495,6 +496,7 @@ private fun Image( modifier = Modifier .aspectRatio(1.3f) .fillMaxSize() + .padding(4.dp) .sharedBoundsRevealWithShapeMorph( sharedContentState = sharedContentState, sharedTransitionScope = sharedTransitionScope, @@ -558,7 +560,7 @@ private fun CartBottomBar(modifier: Modifier = Modifier) { ), ) { it } + fadeIn(tween(300, delayMillis = 300)), exit = slideOutVertically(tween(50)) { it } + - fadeOut(tween(50)), + fadeOut(tween(50)), ), ) { Column { @@ -575,10 +577,11 @@ private fun CartBottomBar(modifier: Modifier = Modifier) { decreaseItemCount = { if (count > 0) updateCount(count - 1) }, increaseItemCount = { updateCount(count + 1) }, ) - Spacer(Modifier.width(16.dp)) + Spacer(Modifier.weight(1f)) Button( onClick = { /* todo */ }, - modifier = Modifier.weight(1f), + modifier = Modifier + .widthIn(max = 200.dp), style = { externalPadding(4.dp) }, 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 index 45ceb187ac..9b482ef17d 100644 --- 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 @@ -26,6 +26,8 @@ import androidx.compose.foundation.style.StyleScope import androidx.compose.foundation.style.StyleStateKey import androidx.compose.foundation.style.disabled import androidx.compose.foundation.style.fillWidth +import androidx.compose.foundation.style.focused +import androidx.compose.foundation.style.hovered import androidx.compose.foundation.style.pressed import androidx.compose.foundation.style.selected import androidx.compose.material3.LocalTextStyle @@ -129,30 +131,60 @@ data class Styles( centerYPercent = 0.5f, ), ) - - contentColor(colors.textInteractive) + contentColor(colors.textSecondary) minSize(58.dp, 48.dp) if (mediaQuery { keyboardKind == UiMediaScope.KeyboardKind.Physical }) { contentPaddingVertical(4.dp) contentPaddingHorizontal(8.dp) shape(shapes.medium) + minHeight(32.dp) + textStyleWithFontFamilyFix(typography.labelMedium) } else { contentPaddingVertical(8.dp) contentPaddingHorizontal(24.dp) + minHeight(40.dp) shape(shapes.small) + textStyleWithFontFamilyFix(typography.labelLarge) } - textStyleWithFontFamilyFix(typography.labelLarge) - dropShadow(Shadow(color = colors.brand, offset = DpOffset(x = 2.dp, y = 4.dp), radius = 12.dp)) - innerShadow(Shadow(color = colors.brand, offset = DpOffset(x = (-6).dp, (-4).dp), radius = 4.dp)) + dropShadow(Shadow(color = colors.brand, offset = DpOffset(x = 0.dp, y = 2.dp), radius = 6.dp)) + innerShadow(Shadow(color = colors.brand.copy(alpha = 0.5f), offset = DpOffset(x = (-6).dp, (-4).dp), radius = 8.dp)) + hovered { + animate { + background(colors.brandLight) + dropShadow(Shadow(color = colors.brand, offset = DpOffset(x = 0.dp, y = 2.dp), radius = 6.dp)) + innerShadow(Shadow(color = colors.brand.copy(alpha = 0.5f), offset = DpOffset(x = (-6).dp, (-2).dp), radius = 8.dp)) + } + } + focused { + animate { + border(4.dp, colors.brand) + } + } pressed { animate { background(Brush.radialGradient(listOf(colors.brand, colors.brand))) dropShadow(Shadow(color = colors.brand, offset = DpOffset(x = 0.dp, y = 0.dp), radius = 0.dp)) innerShadow(Shadow(color = colors.brand, offset = DpOffset(x = (0).dp, (0).dp), radius = 0.dp)) } + focused { + animate { + border(4.dp, colors.brand) + } + } + hovered { + // this state is broken >> + // we don't want to combine these two + // so set the properties to the same + animate { + background(Brush.radialGradient(listOf(colors.brand, colors.brand))) + dropShadow(Shadow(color = colors.brand, offset = DpOffset(x = 0.dp, y = 0.dp), radius = 0.dp)) + innerShadow(Shadow(color = colors.brand, offset = DpOffset(x = (0).dp, (0).dp), radius = 0.dp)) + } + } } + loading { animate { background( @@ -238,7 +270,6 @@ data class Styles( } }, val defaultTextStyle: Style = Style { - contentColor(colors.textPrimary) textStyleWithFontFamilyFix(LocalTextStyle.currentValue) }, val surfaceStyle: Style = Style { From 726f8a966d316d9458b44370e0d73f67b1f1f516 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Wed, 25 Mar 2026 17:11:04 +0000 Subject: [PATCH 19/33] Check for keyboard and mouse attached, then scale down --- .../src/main/java/com/example/jetsnack/ui/theme/Styles.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 index 9b482ef17d..418772a2af 100644 --- 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 @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(ExperimentalFoundationStyleApi::class, ExperimentalMediaQueryApi::class) +@file:OptIn(ExperimentalFoundationStyleApi::class, ExperimentalMediaQueryApi::class, ExperimentalMaterial3ExpressiveApi::class) package com.example.jetsnack.ui.theme @@ -132,8 +132,9 @@ data class Styles( ), ) contentColor(colors.textSecondary) - minSize(58.dp, 48.dp) - if (mediaQuery { keyboardKind == UiMediaScope.KeyboardKind.Physical }) { + minWidth(58.dp) + if (mediaQuery { pointerPrecision == UiMediaScope.PointerPrecision.Fine + && keyboardKind == UiMediaScope.KeyboardKind.Physical }) { contentPaddingVertical(4.dp) contentPaddingHorizontal(8.dp) shape(shapes.medium) From 6b5873b8aa8229cc4c1017d1b584b5f1a3d940d4 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Wed, 25 Mar 2026 18:25:19 +0000 Subject: [PATCH 20/33] Refactor Search Categories to use LazyVerticalGrid This change replaces the custom `VerticalGrid` implementation with the standard Compose `LazyVerticalGrid` to improve list performance and responsiveness in the search screen. ### Summary of changes * **Refactored Search UI**: Migrated the search category list from a `LazyColumn` containing custom layouts to a single `LazyVerticalGrid`. * Used `GridCells.Adaptive(200.dp)` for a more responsive grid layout. * Implemented `GridItemSpan` to allow category headers to span the full width of the grid. * **Component Cleanup**: Deleted the custom `VerticalGrid` implementation in `Grid.kt` as it is no longer needed. * **UI Adjustments**: * Increased the search bar height from `56.dp` to `72.dp`. * Removed the unused `ExperimentalMaterial3ExpressiveApi` opt-in from `Styles.kt`. * Adjusted padding and spacing within the search category items for better alignment. --- .../example/jetsnack/ui/components/Grid.kt | 64 ------------------- .../jetsnack/ui/home/search/Categories.kt | 55 ++++++++-------- .../example/jetsnack/ui/home/search/Search.kt | 3 +- .../com/example/jetsnack/ui/theme/Styles.kt | 2 +- 4 files changed, 28 insertions(+), 96 deletions(-) delete mode 100644 Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Grid.kt diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Grid.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Grid.kt deleted file mode 100644 index 0fd9f27839..0000000000 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Grid.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2020 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.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.Layout - -/** - * A simple grid which lays elements out vertically in evenly sized [columns]. - */ -@Composable -fun VerticalGrid(modifier: Modifier = Modifier, columns: Int = 2, content: @Composable () -> Unit) { - Layout( - content = content, - modifier = modifier, - ) { measurables, constraints -> - val itemWidth = constraints.maxWidth / columns - // Keep given height constraints, but set an exact width - val itemConstraints = constraints.copy( - minWidth = itemWidth, - maxWidth = itemWidth, - ) - // Measure each item with these constraints - val placeables = measurables.map { it.measure(itemConstraints) } - // Track each columns height so we can calculate the overall height - val columnHeights = Array(columns) { 0 } - placeables.forEachIndexed { index, placeable -> - val column = index % columns - columnHeights[column] += placeable.height - } - val height = (columnHeights.maxOrNull() ?: constraints.minHeight) - .coerceAtMost(constraints.maxHeight) - layout( - width = constraints.maxWidth, - height = height, - ) { - // Track the Y co-ord per column we have placed up to - val columnY = Array(columns) { 0 } - placeables.forEachIndexed { index, placeable -> - val column = index % columns - placeable.placeRelative( - x = column * itemWidth, - y = columnY[column], - ) - columnY[column] += placeable.height - } - } - } -} 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 58e14ba8a7..d636a03449 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 @@ -20,7 +20,7 @@ import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize @@ -28,8 +28,10 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.style.Style import androidx.compose.foundation.style.rememberUpdatedStyleState @@ -55,7 +57,6 @@ import com.example.jetsnack.model.SearchCategory import com.example.jetsnack.model.SearchCategoryCollection import com.example.jetsnack.ui.components.SnackImage import com.example.jetsnack.ui.components.Text -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 @@ -64,34 +65,31 @@ import kotlin.math.max @Composable fun SearchCategories(categories: List) { - LazyColumn { - itemsIndexed(categories) { index, collection -> - SearchCategoryCollection(collection, index) - } - } - Spacer(Modifier.height(8.dp)) -} - -@Composable -private fun SearchCategoryCollection(collection: SearchCategoryCollection, index: Int, modifier: Modifier = Modifier) { - Column(modifier) { - Text( - text = collection.name, - style = { - textStyleWithFontFamilyFix(typography.titleLarge) - contentColor(colors.textSecondary) - }, - modifier = Modifier - .heightIn(min = 56.dp) - .padding(horizontal = 16.dp, vertical = 4.dp) - .wrapContentHeight(), - ) - VerticalGrid(Modifier.padding(horizontal = 8.dp)) { + LazyVerticalGrid( + columns = GridCells.Adaptive(200.dp), + contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp), + ) { + categories.forEachIndexed { index, collection -> + item(span = { GridItemSpan(maxLineSpan) }) { + Text( + text = collection.name, + style = { + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.textSecondary) + }, + modifier = Modifier + .heightIn(min = 56.dp) + .padding(horizontal = 8.dp, vertical = 4.dp) + .wrapContentHeight(), + ) + } val borderColor = when (index % 2) { 0 -> Color(0xFF8BDEBE) else -> Color(0xffFFC8A4) } - collection.categories.forEach { category -> + items(collection.categories, key = { + it.name + }) { category -> SearchCategory( category = category, modifier = Modifier.padding(4.dp), @@ -101,7 +99,6 @@ private fun SearchCategoryCollection(collection: SearchCategoryCollection, index ) } } - Spacer(Modifier.height(4.dp)) } } 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 e284e7951b..4cc644d22b 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 @@ -77,7 +77,6 @@ fun Search(onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, searching = state.searching, ) JetsnackDivider() - LaunchedEffect(state.query.text) { state.searching = true state.searchResults = SearchRepo.search(state.query.text) @@ -178,7 +177,7 @@ private fun SearchBar( }, modifier = modifier .fillMaxWidth() - .height(56.dp) + .height(72.dp) .padding(horizontal = 24.dp, vertical = 8.dp), ) { Box(Modifier.fillMaxSize()) { 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 index 418772a2af..4eee80a3e5 100644 --- 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 @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(ExperimentalFoundationStyleApi::class, ExperimentalMediaQueryApi::class, ExperimentalMaterial3ExpressiveApi::class) +@file:OptIn(ExperimentalFoundationStyleApi::class, ExperimentalMediaQueryApi::class) package com.example.jetsnack.ui.theme From 04ef3630be454519830471d109df8d95b615d584 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Wed, 25 Mar 2026 18:38:30 +0000 Subject: [PATCH 21/33] Add responsive horizontal padding to JetsnackBottomBar This update uses the `mediaQuery` API to apply a 200dp horizontal padding to the bottom navigation bar when the window width is greater than 600dp. --- .../java/com/example/jetsnack/ui/home/Home.kt | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) 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 2b539dff93..de7e106e2c 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 @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalMediaQueryApi::class) + package com.example.jetsnack.ui.home import androidx.annotation.DrawableRes @@ -29,7 +31,6 @@ import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Spacer @@ -40,7 +41,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.style.Style import androidx.compose.foundation.style.styleable import androidx.compose.material3.Icon import androidx.compose.runtime.Composable @@ -48,11 +48,11 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.ExperimentalMediaQueryApi 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.Shape import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.Layout @@ -60,12 +60,12 @@ import androidx.compose.ui.layout.MeasureResult import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.mediaQuery 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 -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.util.lerp import androidx.core.os.ConfigurationCompat @@ -94,21 +94,21 @@ fun NavGraphBuilder.composableWithCompositionLocal( arguments: List = emptyList(), deepLinks: List = emptyList(), enterTransition: ( - @JvmSuppressWildcards AnimatedContentTransitionScope.() -> EnterTransition? + @JvmSuppressWildcards AnimatedContentTransitionScope.() -> EnterTransition? )? = { fadeIn(nonSpatialExpressiveSpring()) }, exitTransition: ( - @JvmSuppressWildcards AnimatedContentTransitionScope.() -> ExitTransition? + @JvmSuppressWildcards AnimatedContentTransitionScope.() -> ExitTransition? )? = { fadeOut(nonSpatialExpressiveSpring()) }, popEnterTransition: ( - @JvmSuppressWildcards AnimatedContentTransitionScope.() -> EnterTransition? + @JvmSuppressWildcards AnimatedContentTransitionScope.() -> EnterTransition? )? = enterTransition, popExitTransition: ( - @JvmSuppressWildcards AnimatedContentTransitionScope.() -> ExitTransition? + @JvmSuppressWildcards AnimatedContentTransitionScope.() -> ExitTransition? )? = exitTransition, content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit, @@ -183,6 +183,9 @@ fun JetsnackBottomBar( style = { background(color) contentColor(contentColor) + if (mediaQuery { windowWidth > 600.dp }) { + contentPaddingHorizontal(200.dp) + } }, ) { val springSpec = spatialExpressiveSpring() @@ -231,7 +234,8 @@ fun JetsnackBottomBar( selected = selected, onSelected = { navigateToRoute(section.route) }, animSpec = springSpec, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 8.dp) .clip(BottomNavIndicatorShape), ) } @@ -423,6 +427,7 @@ private val TextIconSpacing = 2.dp private val BottomNavHeight = 56.dp private val BottomNavLabelTransformOrigin = TransformOrigin(0f, 0.5f) private val BottomNavIndicatorShape = RoundedCornerShape(percent = 50) + @Preview @Composable private fun JetsnackBottomNavPreview() { From 1ad55193c71bd8d4663d731f62fd454ca0af3656 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Wed, 25 Mar 2026 19:56:17 +0000 Subject: [PATCH 22/33] Migrated Snacks to use one source of truth instead of two. Theming each different snack differently, added hover and focused effects. --- .../example/jetsnack/model/SnackCollection.kt | 3 +- .../example/jetsnack/ui/components/Card.kt | 12 +- .../example/jetsnack/ui/components/Snacks.kt | 466 +++++++++--------- .../java/com/example/jetsnack/ui/home/Feed.kt | 3 +- .../com/example/jetsnack/ui/home/cart/Cart.kt | 9 +- .../jetsnack/ui/snackdetail/SnackDetail.kt | 19 +- .../com/example/jetsnack/ui/theme/Styles.kt | 25 +- .../jetsnack/ui/utils/PreviewWrapper.kt | 14 +- 8 files changed, 280 insertions(+), 271 deletions(-) diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt index da4ecd49b3..aa9bfeae6d 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt @@ -22,7 +22,7 @@ import kotlin.random.Random @Immutable data class SnackCollection(val id: Long, val name: String, val snacks: List, val type: CollectionType = CollectionType.Normal) -enum class CollectionType { Normal, Highlight } +enum class CollectionType { Normal, Highlight, Card } /** * A fake repo @@ -61,6 +61,7 @@ private val popular = SnackCollection( private val wfhFavs = tastyTreats.copy( id = Random.nextLong(), name = "WFH favourites", + type = CollectionType.Card ) private val newlyAdded = popular.copy( 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 5cc626f2ba..28a4707d08 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 @@ -19,21 +19,31 @@ package com.example.jetsnack.ui.components import android.content.res.Configuration +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.padding 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.then import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.theme.JetsnackTheme @Composable -fun JetsnackCard(modifier: Modifier = Modifier, style: Style = Style, content: @Composable () -> Unit) { +fun JetsnackCard(modifier: Modifier = Modifier, + style: Style = Style, + interactionSource: InteractionSource = remember { MutableInteractionSource() }, + styleState: StyleState = rememberUpdatedStyleState(interactionSource), + content: @Composable () -> Unit) { Surface( modifier = modifier, style = JetsnackTheme.styles.cardStyle then style, + styleState = styleState, content = content, ) } 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 0724a8ce96..54ce64ad29 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,9 @@ * limitations under the License. */ -@file:OptIn(ExperimentalSharedTransitionApi::class, ExperimentalFoundationStyleApi::class, ExperimentalMaterial3ExpressiveApi::class) +@file:OptIn(ExperimentalSharedTransitionApi::class, ExperimentalFoundationStyleApi::class, ExperimentalMaterial3ExpressiveApi::class, + ExperimentalMediaQueryApi::class +) package com.example.jetsnack.ui.components @@ -32,6 +34,7 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -43,6 +46,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items @@ -52,22 +57,30 @@ 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.focused +import androidx.compose.foundation.style.hovered import androidx.compose.foundation.style.then import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.graphics.shapes.Morph import coil.compose.AsyncImage @@ -85,22 +98,69 @@ 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 +import com.example.jetsnack.ui.utils.JetsnackThemeWrapper import com.example.jetsnack.ui.utils.SnackPolygons +import com.example.jetsnack.ui.utils.UiMediaScopeWrapper import com.example.jetsnack.ui.utils.asShape import com.example.jetsnack.ui.utils.formatPrice import com.example.jetsnack.ui.utils.sharedBoundsRevealWithShapeMorph private val HighlightCardWidth = 170.dp +private val highlightGlowCardStyle = Style { + background(colors.brandLight) + border(0.dp, colors.brandLight) + hovered { + animate { + scale(1.05f) + dropShadow(Shadow(offset = DpOffset(0.dp, 2.dp), radius = 6.dp, color = colors.brand)) + innerShadow(Shadow(offset = DpOffset((-6).dp, (-2).dp), radius = 8.dp, color = colors.brand.copy(alpha = 0.5f))) + } + } + focused { + animate { + scale(1.05f) + dropShadow(Shadow(offset = DpOffset(0.dp, 2.dp), radius = 6.dp, color = colors.brand)) + innerShadow(Shadow(offset = DpOffset((-6).dp, (-2).dp), radius = 8.dp, color = colors.brand.copy(alpha = 0.5f))) + } + } +} +private val normalCardStyle = Style { + background(Color.Transparent) + hovered { + animate { + scale(1.05f) + } + } + focused { + animate { + scale(1.05f) + } + } +} + +private val plainCardStyle = Style { + background(colors.cardHighlightBackground) + clip(true) + border(1.dp, colors.cardHighlightBorder) + hovered { + animate { + scale(1.05f) + } + } + focused { + animate { + scale(1.05f) + } + } +} + @Composable fun SnackCollection( snackCollection: SnackCollection, onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, - index: Int = 0, - highlight: Boolean = true, ) { Column(modifier = modifier) { Row( @@ -132,157 +192,53 @@ fun SnackCollection( ) } } - if (highlight && snackCollection.type == CollectionType.Highlight) { - HighlightedSnacks(snackCollection.id, snackCollection.snacks, onSnackClick) - } else { - Snacks(snackCollection.id, snackCollection.snacks, onSnackClick) - } - } -} - -@Composable -private fun HighlightedSnacks( - snackCollectionId: Long, - snacks: List, - onSnackClick: (Long, String) -> Unit, - modifier: Modifier = Modifier, -) { - val rowState = rememberLazyListState() - LazyRow( - state = rowState, - modifier = modifier, - horizontalArrangement = Arrangement.spacedBy(16.dp), - contentPadding = PaddingValues(start = 24.dp, end = 24.dp), - ) { - itemsIndexed(snacks) { _, snack -> - HighlightSnackItem( - snackCollectionId = snackCollectionId, - snack = snack, - onSnackClick = onSnackClick, - ) - } - } -} - -@Composable -private fun Snacks(snackCollectionId: Long, snacks: List, onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier) { - LazyRow( - modifier = modifier, - contentPadding = PaddingValues(start = 12.dp, end = 12.dp), - ) { - items(snacks) { snack -> - SnackItem(snack, snackCollectionId, onSnackClick) - } - } -} - -@Composable -fun SnackItem(snack: Snack, snackCollectionId: Long, onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier) { - Surface( - style = { - shape(shapes.medium) - }, - modifier = modifier.padding( - start = 4.dp, - end = 4.dp, - bottom = 8.dp, - ), - ) { - val sharedTransitionScope = LocalSharedTransitionScope.current - ?: throw IllegalStateException("No sharedTransitionScope found") - val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current - ?: throw IllegalStateException("No animatedVisibilityScope found") + LazyRow( + state = rememberLazyListState(), + modifier = modifier.padding(bottom = 16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + contentPadding = PaddingValues(start = 24.dp, end = 24.dp), + ) { + itemsIndexed(snacks) { _, snack -> + when (snackCollection.type) { + CollectionType.Normal -> + SnackItem( + snackCollectionId = snackCollection.id, + snack = snack, + onSnackClick = onSnackClick, + showTagLine = false, + style = normalCardStyle, + ) - with(sharedTransitionScope) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .clickable( - onClick = { - onSnackClick(snack.id, snackCollectionId.toString()) - }, - ) - .padding(8.dp), - ) { - val sharedContentState = rememberSharedContentState( - key = SnackSharedElementKey( - snackId = snack.id, - origin = snackCollectionId.toString(), - type = SnackSharedElementType.Image, - ), - ) - val restingRoundedPolygon = SnackPolygons.snackItemPolygon - val targetRoundedPolygon = SnackPolygons.pillIntermediatePolygon - val morph = remember { Morph(restingRoundedPolygon, targetRoundedPolygon) } - val progress = animatedVisibilityScope.transition.animateFloat( - transitionSpec = { - tween(300, easing = LinearEasing) - }, - ) { - when (it) { - EnterExitState.PreEnter -> 1f - EnterExitState.Visible -> 0f - EnterExitState.PostExit -> 1f - } - }.value + CollectionType.Highlight -> + SnackItem( + snackCollectionId = snackCollection.id, + snack = snack, + onSnackClick = onSnackClick, + style = highlightGlowCardStyle, + ) - SnackImage( - imageRes = snack.imageRes, - contentDescription = null, - style = { - val shape = if (sharedContentState.isMatchFound) { - morph.asShape(progress) - } else { - restingRoundedPolygon.asShape() - } - shape(shape) - }, - modifier = Modifier - .size(width = 110.dp, height = 80.dp) - .sharedBoundsRevealWithShapeMorph( - sharedContentState = sharedContentState, - sharedTransitionScope = sharedTransitionScope, - animatedVisibilityScope = animatedVisibilityScope, - boundsTransform = snackDetailBoundsTransform, - targetShape = targetRoundedPolygon, - restingShape = restingRoundedPolygon, - ), - ) - Text( - text = snack.name, - style = { - textStyleWithFontFamilyFix(typography.titleMedium) - contentColor(colors.textSecondary) - }, - modifier = Modifier - .padding(top = 8.dp) - .wrapContentWidth() - .sharedBounds( - rememberSharedContentState( - key = SnackSharedElementKey( - snackId = snack.id, - origin = snackCollectionId.toString(), - type = SnackSharedElementType.Title, - ), - ), - animatedVisibilityScope = animatedVisibilityScope, - enter = fadeIn(nonSpatialExpressiveSpring()), - exit = fadeOut(nonSpatialExpressiveSpring()), - resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds(), - boundsTransform = snackDetailBoundsTransform, - ), - ) + CollectionType.Card -> + SnackItem( + snackCollectionId = snackCollection.id, + snack = snack, + onSnackClick = onSnackClick, + style = plainCardStyle, + ) + } } } } } @Composable -private fun HighlightSnackItem( +private fun SnackItem( snackCollectionId: Long, snack: Snack, - onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, + style: Style = Style, + showTagLine: Boolean = true, + showAddButton: Boolean = false, + onSnackClick: (Long, String) -> Unit, ) { val sharedTransitionScope = LocalSharedTransitionScope.current ?: throw IllegalStateException("No Scope found") @@ -297,16 +253,15 @@ private fun HighlightSnackItem( EnterExitState.PostExit -> 20.dp } } + val interactionSource = remember { MutableInteractionSource() } JetsnackCard( style = Style { - background(colors.cardHighlightBackground) shape(RoundedCornerShape(roundedCornerAnimation)) - clip(true) - border(1.dp, colors.cardHighlightBorder) - size(width = HighlightCardWidth, height = 200.dp) - }, + width(HighlightCardWidth) + } then style, + interactionSource = interactionSource, modifier = modifier - .padding(bottom = 16.dp) + .wrapContentHeight() .sharedBounds( sharedContentState = rememberSharedContentState( key = SnackSharedElementKey( @@ -324,19 +279,20 @@ private fun HighlightSnackItem( ), enter = fadeIn(), exit = fadeOut(), + ) + .clickable( + onClick = { + onSnackClick( + snack.id, + snackCollectionId.toString(), + ) + }, + interactionSource = interactionSource, + indication = null, ), ) { Column( - modifier = Modifier - .clickable( - onClick = { - onSnackClick( - snack.id, - snackCollectionId.toString(), - ) - }, - ) - .fillMaxSize(), + modifier = Modifier.fillMaxWidth().wrapContentHeight(), ) { val sharedContentState = rememberSharedContentState( key = SnackSharedElementKey( @@ -383,61 +339,89 @@ private fun HighlightSnackItem( .fillMaxWidth() .size(120.dp), ) - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = snack.name, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = { - textStyleWithFontFamilyFix(typography.titleLarge) - contentColor(colors.textSecondary) - }, - modifier = Modifier - .padding(horizontal = 16.dp) - .sharedBounds( - rememberSharedContentState( - key = SnackSharedElementKey( - snackId = snack.id, - origin = snackCollectionId.toString(), - type = SnackSharedElementType.Title, - ), - ), - animatedVisibilityScope = animatedVisibilityScope, - enter = fadeIn(nonSpatialExpressiveSpring()), - exit = fadeOut(nonSpatialExpressiveSpring()), - boundsTransform = snackDetailBoundsTransform, - resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds(), - ) - .wrapContentWidth(), - ) - Spacer(modifier = Modifier.height(4.dp)) - - Text( - text = formatPrice(snack.price), - style = { - textStyleWithFontFamilyFix(typography.bodyLarge) - contentColor(colors.textHelp) - }, - modifier = Modifier - .padding(horizontal = 16.dp) - .sharedBounds( - rememberSharedContentState( - key = SnackSharedElementKey( - snackId = snack.id, - origin = snackCollectionId.toString(), - type = SnackSharedElementType.Price, - ), - ), - animatedVisibilityScope = animatedVisibilityScope, - enter = fadeIn(nonSpatialExpressiveSpring()), - exit = fadeOut(nonSpatialExpressiveSpring()), - boundsTransform = snackDetailBoundsTransform, - resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds(), + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = snack.name, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = { + textStyleWithFontFamilyFix(typography.titleLarge) + contentColor(colors.textSecondary) + }, + modifier = Modifier + .padding(start = 16.dp, end = 2.dp) + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = snackCollectionId.toString(), + type = SnackSharedElementType.Title, + ), + ), + animatedVisibilityScope = animatedVisibilityScope, + enter = fadeIn(nonSpatialExpressiveSpring()), + exit = fadeOut(nonSpatialExpressiveSpring()), + boundsTransform = snackDetailBoundsTransform, + resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds(), + ) + .wrapContentWidth(), ) - .wrapContentWidth(), - ) + if (showTagLine) { + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = formatPrice(snack.price), + style = { + textStyleWithFontFamilyFix(typography.bodyLarge) + contentColor(colors.textHelp) + }, + modifier = Modifier + .padding(horizontal = 16.dp) + .sharedBounds( + rememberSharedContentState( + key = SnackSharedElementKey( + snackId = snack.id, + origin = snackCollectionId.toString(), + type = SnackSharedElementType.Price, + ), + ), + animatedVisibilityScope = animatedVisibilityScope, + enter = fadeIn(nonSpatialExpressiveSpring()), + exit = fadeOut(nonSpatialExpressiveSpring()), + boundsTransform = snackDetailBoundsTransform, + resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds(), + ) + .wrapContentWidth(), + ) + } + } + if (showAddButton) { + Button( + onClick = { + }, + style = { + shape(CircleShape) + size(36.dp) + background(colors.brand) + dropShadow(Shadow(0.dp)) + contentPadding(8.dp) + externalPadding(8.dp) + }, + modifier = Modifier + ) { + Icon( + painterResource(R.drawable.ic_add), + modifier = Modifier.size(24.dp), + tint = JetsnackTheme.colors.textSecondary, + contentDescription = stringResource(R.string.add_to_cart_content_description), + ) + } + } + } } } } @@ -481,17 +465,59 @@ fun SnackImage( @Preview("dark theme", uiMode = UI_MODE_NIGHT_YES) @Preview("large font", fontScale = 2f) @Composable -fun SnackCardPreview() { +fun SnackCardPreviewCard() { val snack = snacks.first() - JetsnackPreviewWrapper { - HighlightSnackItem( - snackCollectionId = 1, - snack = snack, - onSnackClick = { _, _ -> }, - ) + JetsnackThemeWrapper { + UiMediaScopeWrapper { + SnackItem( + snackCollectionId = 1, + snack = snack, + style = normalCardStyle, + onSnackClick = { _, _ -> }, + ) + } } } +@Preview("default") +@Preview("dark theme", uiMode = UI_MODE_NIGHT_YES) +@Preview("large font", fontScale = 2f) +@Composable +fun SnackCardPreviewHighlight() { + val snack = snacks.first() + JetsnackThemeWrapper { + UiMediaScopeWrapper { + SnackItem( + snackCollectionId = 1, + snack = snack, + style = highlightGlowCardStyle, + showAddButton = true, + onSnackClick = { _, _ -> }, + ) + } + } +} + +@Preview("default") +@Preview("dark theme", uiMode = UI_MODE_NIGHT_YES) +@Preview("large font", fontScale = 2f) +@Composable +fun SnackCardPreviewPlain() { + val snack = snacks.first() + JetsnackThemeWrapper { + UiMediaScopeWrapper { + SnackItem( + snackCollectionId = 1, + snack = snack, + showTagLine = false, + style = normalCardStyle, + onSnackClick = { _, _ -> }, + ) + } + } +} + + @Composable fun JetsnackPreviewWrapper(content: @Composable () -> Unit) { JetsnackTheme { 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 25b19e0e07..84a7311f73 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 @@ -132,8 +132,7 @@ private fun SnackCollectionList( SnackCollection( snackCollection = snackCollection, - onSnackClick = onSnackClick, - index = index, + onSnackClick = onSnackClick ) } } 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 f9e505f531..96d5e2d121 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 @@ -86,7 +86,7 @@ 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 -import com.example.jetsnack.ui.utils.SharedElementPreviewWrapper +import com.example.jetsnack.ui.utils.JetsnackThemeWrapper import com.example.jetsnack.ui.utils.UiMediaScopeWrapper import com.example.jetsnack.ui.utils.formatPrice import kotlin.math.roundToInt @@ -218,8 +218,7 @@ private fun CartContent( placementSpec = itemPlacementSpec, ), snackCollection = inspiredByCart, - onSnackClick = onSnackClick, - highlight = false, + onSnackClick = onSnackClick ) Spacer(Modifier.height(56.dp)) } @@ -505,9 +504,8 @@ private fun CheckoutBar(modifier: Modifier = Modifier) { @Preview("large font", fontScale = 2f) @Composable private fun CartPreview() { - JetsnackTheme { + JetsnackThemeWrapper { UiMediaScopeWrapper { - SharedElementPreviewWrapper { Cart( orderLines = SnackRepo.getCart(), removeSnack = {}, @@ -516,7 +514,6 @@ private fun CartPreview() { inspiredByCart = SnackRepo.getInspiredByCart(), onSnackClick = { _, _ -> }, ) - } } } } 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 56431d47ff..105892978a 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 @@ -115,7 +115,7 @@ 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.SharedElementPreviewWrapper +import com.example.jetsnack.ui.utils.JetsnackThemeWrapper import com.example.jetsnack.ui.utils.SnackPolygons import com.example.jetsnack.ui.utils.UiMediaScopeWrapper import com.example.jetsnack.ui.utils.asShape @@ -327,7 +327,6 @@ private fun Body(related: List, scroll: ScrollState) { SnackCollection( snackCollection = snackCollection, onSnackClick = { _, _ -> }, - highlight = false, ) } } @@ -607,15 +606,13 @@ private fun CartBottomBar(modifier: Modifier = Modifier) { @Preview("large font", fontScale = 2f) @Composable private fun SnackDetailPreview() { - JetsnackTheme { - SharedElementPreviewWrapper { - UiMediaScopeWrapper { - SnackDetail( - snackId = 1L, - origin = "details", - upPress = { }, - ) - } + JetsnackThemeWrapper { + UiMediaScopeWrapper { + SnackDetail( + snackId = 1L, + origin = "details", + upPress = { }, + ) } } } 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 index 4eee80a3e5..e354e620a3 100644 --- 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 @@ -53,29 +53,6 @@ import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix -fun StyleScope.adaptiveFontSize(fontSize: TextUnit) { - var scaleFactor = when (LocalUiMediaScope.currentValue.viewingDistance) { - ViewingDistance.Near -> 1f - ViewingDistance.Medium -> 1.72f - ViewingDistance.Far -> 1.5f - else -> 1f - } - scaleFactor = when (LocalUiMediaScope.currentValue.pointerPrecision) { - UiMediaScope.PointerPrecision.Coarse -> scaleFactor * 1f - - UiMediaScope.PointerPrecision.Blunt -> scaleFactor * 0.66f - - UiMediaScope.PointerPrecision.Fine -> scaleFactor * 1f - - UiMediaScope.PointerPrecision.None -> scaleFactor - - else -> { - scaleFactor - } - } - fontSize(fontSize * scaleFactor) -} - /** * Creates an elliptical radial gradient brush that emulates the CSS radial-gradient spec. * @@ -175,7 +152,7 @@ data class Styles( } } hovered { - // this state is broken >> + // TODO this state is broken - await API changes on animation changes // we don't want to combine these two // so set the properties to the same animate { diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt index c6b1cb03e1..4589c6bfb5 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt @@ -39,12 +39,14 @@ import com.example.jetsnack.ui.home.cart.Cart import com.example.jetsnack.ui.theme.JetsnackTheme @Composable -fun SharedElementPreviewWrapper(content: @Composable () -> Unit) { - SharedTransitionLayout { - AnimatedVisibility(true) { - CompositionLocalProvider(LocalSharedTransitionScope provides this@SharedTransitionLayout) { - CompositionLocalProvider(LocalNavAnimatedVisibilityScope provides this@AnimatedVisibility) { - content() +fun JetsnackThemeWrapper(content: @Composable () -> Unit) { + JetsnackTheme { + SharedTransitionLayout { + AnimatedVisibility(true) { + CompositionLocalProvider(LocalSharedTransitionScope provides this@SharedTransitionLayout) { + CompositionLocalProvider(LocalNavAnimatedVisibilityScope provides this@AnimatedVisibility) { + content() + } } } } From 5e428f57672953e1b5415175ac45bbaef2e80594 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Wed, 25 Mar 2026 20:49:18 +0000 Subject: [PATCH 23/33] Small UI tweaks on the Snacks to differentiate between different styles, Removed custom layout for categories to properly use the image shape. --- .../example/jetsnack/ui/components/Snacks.kt | 65 +++++++++++++------ .../jetsnack/ui/home/search/Categories.kt | 49 +++++--------- .../jetsnack/ui/snackdetail/SnackDetail.kt | 4 +- .../jetsnack/ui/utils/SharedBoundsMorph.kt | 14 ++-- 4 files changed, 69 insertions(+), 63 deletions(-) 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 54ce64ad29..beba8834b7 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,8 +14,9 @@ * limitations under the License. */ -@file:OptIn(ExperimentalSharedTransitionApi::class, ExperimentalFoundationStyleApi::class, ExperimentalMaterial3ExpressiveApi::class, - ExperimentalMediaQueryApi::class +@file:OptIn( + ExperimentalSharedTransitionApi::class, ExperimentalFoundationStyleApi::class, ExperimentalMaterial3ExpressiveApi::class, + ExperimentalMediaQueryApi::class, ) package com.example.jetsnack.ui.components @@ -40,6 +41,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -47,10 +49,8 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape @@ -59,11 +59,11 @@ import androidx.compose.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.foundation.style.Style import androidx.compose.foundation.style.focused import androidx.compose.foundation.style.hovered +import androidx.compose.foundation.style.pressed import androidx.compose.foundation.style.then import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -72,17 +72,20 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.graphics.shapes.Morph +import androidx.graphics.shapes.RoundedPolygon import coil.compose.AsyncImage import coil.request.ImageRequest import com.example.jetsnack.R @@ -128,6 +131,9 @@ private val highlightGlowCardStyle = Style { } private val normalCardStyle = Style { background(Color.Transparent) + width(100.dp) + contentPadding(2.dp) + textAlign(TextAlign.Center) hovered { animate { scale(1.05f) @@ -138,12 +144,20 @@ private val normalCardStyle = Style { scale(1.05f) } } + pressed { + background(colors.uiFloated.copy(alpha = 0.5f)) + } +} +private val normalTextStyle = Style { + textStyleWithFontFamilyFix(typography.titleSmall) + textAlign(TextAlign.Center) } private val plainCardStyle = Style { background(colors.cardHighlightBackground) clip(true) border(1.dp, colors.cardHighlightBorder) + textAlign(TextAlign.Center) hovered { animate { scale(1.05f) @@ -172,7 +186,7 @@ fun SnackCollection( Text( text = snackCollection.name, style = { - textStyleWithFontFamilyFix(typography.headlineSmall) + textStyleWithFontFamilyFix(typography.bodyLarge) contentColor(colors.textPrimary) }, maxLines = 1, @@ -198,7 +212,7 @@ fun SnackCollection( horizontalArrangement = Arrangement.spacedBy(16.dp), contentPadding = PaddingValues(start = 24.dp, end = 24.dp), ) { - itemsIndexed(snacks) { _, snack -> + itemsIndexed(snacks, key = { _, item -> item.id }) { _, snack -> when (snackCollection.type) { CollectionType.Normal -> SnackItem( @@ -206,7 +220,10 @@ fun SnackCollection( snack = snack, onSnackClick = onSnackClick, showTagLine = false, + imageShape = SnackPolygons.snackItemPolygonRounded, style = normalCardStyle, + snackTextStyle = normalTextStyle, + imageAspectRatio = 4f / 3f, ) CollectionType.Highlight -> @@ -214,6 +231,7 @@ fun SnackCollection( snackCollectionId = snackCollection.id, snack = snack, onSnackClick = onSnackClick, + showAddButton = true, style = highlightGlowCardStyle, ) @@ -223,6 +241,7 @@ fun SnackCollection( snack = snack, onSnackClick = onSnackClick, style = plainCardStyle, + imageAspectRatio = 16 / 9f, ) } } @@ -236,6 +255,9 @@ private fun SnackItem( snack: Snack, modifier: Modifier = Modifier, style: Style = Style, + snackTextStyle: Style = Style, + imageShape: RoundedPolygon = SnackPolygons.snackItemPolygon, + imageAspectRatio: Float = 1f, showTagLine: Boolean = true, showAddButton: Boolean = false, onSnackClick: (Long, String) -> Unit, @@ -292,7 +314,9 @@ private fun SnackItem( ), ) { Column( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), ) { val sharedContentState = rememberSharedContentState( key = SnackSharedElementKey( @@ -301,9 +325,8 @@ private fun SnackItem( type = SnackSharedElementType.Image, ), ) - val restingRoundedPolygon = SnackPolygons.snackDetailPolygon - val targetRoundedPolygon = SnackPolygons.pillIntermediatePolygon - val morph = remember { Morph(restingRoundedPolygon, targetRoundedPolygon) } + val targetRoundedPolygon = SnackPolygons.snackDetailPolygon + val morph = remember { Morph(imageShape, targetRoundedPolygon) } val progress = animatedVisibilityScope.transition.animateFloat( transitionSpec = { tween(300, easing = LinearEasing) @@ -319,11 +342,11 @@ private fun SnackItem( SnackImage( imageRes = snack.imageRes, contentDescription = null, - style = { + style = Style { val shape = if (sharedContentState.isMatchFound) { morph.asShape(progress) } else { - restingRoundedPolygon.asShape() + imageShape.asShape() } shape(shape) }, @@ -334,10 +357,10 @@ private fun SnackItem( animatedVisibilityScope = animatedVisibilityScope, boundsTransform = snackDetailBoundsTransform, targetShape = targetRoundedPolygon, - restingShape = restingRoundedPolygon, + restingShape = imageShape, ) .fillMaxWidth() - .size(120.dp), + .aspectRatio(imageAspectRatio), ) Spacer(modifier = Modifier.height(8.dp)) Row( @@ -349,12 +372,13 @@ private fun SnackItem( text = snack.name, maxLines = 1, overflow = TextOverflow.Ellipsis, - style = { + style = Style { textStyleWithFontFamilyFix(typography.titleLarge) contentColor(colors.textSecondary) - }, + textAlign(TextAlign.Start) + } then snackTextStyle, modifier = Modifier - .padding(start = 16.dp, end = 2.dp) + .padding(horizontal = 16.dp) .sharedBounds( rememberSharedContentState( key = SnackSharedElementKey( @@ -369,7 +393,7 @@ private fun SnackItem( boundsTransform = snackDetailBoundsTransform, resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds(), ) - .wrapContentWidth(), + .fillMaxWidth(), ) if (showTagLine) { Spacer(modifier = Modifier.height(4.dp)) @@ -411,7 +435,7 @@ private fun SnackItem( contentPadding(8.dp) externalPadding(8.dp) }, - modifier = Modifier + modifier = Modifier, ) { Icon( painterResource(R.drawable.ic_add), @@ -473,6 +497,7 @@ fun SnackCardPreviewCard() { snackCollectionId = 1, snack = snack, style = normalCardStyle, + showTagLine = false, onSnackClick = { _, _ -> }, ) } 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 d636a03449..cc1fbbcd82 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 @@ -21,8 +21,10 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn @@ -41,6 +43,7 @@ import androidx.compose.material3.MaterialTheme 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.draw.shadow @@ -49,6 +52,7 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.VerticalAlignmentLine import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp @@ -66,7 +70,7 @@ import kotlin.math.max @Composable fun SearchCategories(categories: List) { LazyVerticalGrid( - columns = GridCells.Adaptive(200.dp), + columns = GridCells.Adaptive(150.dp), contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp), ) { categories.forEachIndexed { index, collection -> @@ -110,7 +114,8 @@ private const val CategoryTextProportion = 0.55f private fun SearchCategory(category: SearchCategory, modifier: Modifier = Modifier, style: Style = Style) { val interactionSource = remember { MutableInteractionSource() } val styleState = rememberUpdatedStyleState(interactionSource) - Layout( + Row( + verticalAlignment = Alignment.CenterVertically, modifier = modifier .aspectRatio(1.85f) .styleable( @@ -120,20 +125,22 @@ private fun SearchCategory(category: SearchCategory, modifier: Modifier = Modifi clip(true) background(colors.uiBackground) dropShadow(Shadow(color = Color(0xffE5E1E2), radius = 8.dp)) - textStyleWithFontFamilyFix(typography.titleSmall) - contentColor(colors.textPrimary) } then style, ) .clickable( interactionSource = interactionSource, indication = ripple(), - ) { /* todo */ }, - content = { + ) { /* todo */ } ){ Text( text = category.name, + style = { + textStyleWithFontFamilyFix(typography.titleSmall) + contentColor(colors.textPrimary) + }, modifier = Modifier .padding(4.dp) - .padding(start = 8.dp), + .padding(start = 8.dp) + .weight(1f, fill = true) ) SnackImage( imageRes = category.imageRes, @@ -141,33 +148,11 @@ private fun SearchCategory(category: SearchCategory, modifier: Modifier = Modifi style = { shape(RoundedCornerShape(topStartPercent = 48)) }, - modifier = Modifier.fillMaxSize(), - ) - }, - ) { measurables, constraints -> - // Text given a set proportion of width (which is determined by the aspect ratio) - val textWidth = (constraints.maxWidth * CategoryTextProportion).toInt() - val textPlaceable = measurables[0].measure(Constraints.fixedWidth(textWidth)) - - // Image is sized to the larger of height of item, or a minimum value - // i.e. may appear larger than item (but clipped to the item bounds) - val imageSize = max(MinImageSize.roundToPx(), constraints.maxHeight) - val imagePlaceable = measurables[1].measure(Constraints.fixed(imageSize, imageSize)) - layout( - width = constraints.maxWidth, - height = constraints.minHeight, - ) { - textPlaceable.placeRelative( - x = 0, - y = (constraints.maxHeight - textPlaceable.height) / 2, // centered - ) - imagePlaceable.placeRelative( - // image is placed to end of text i.e. will overflow to the end (but be clipped) - x = textWidth, - y = (constraints.maxHeight - imagePlaceable.height) / 2, // centered + modifier = Modifier.fillMaxSize() + .weight(1f) + .defaultMinSize(minWidth = MinImageSize), ) } - } } @Preview("default") 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 105892978a..d15a70ebbe 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 @@ -456,8 +456,8 @@ private fun Image( ?: throw IllegalStateException("No animatedVisibilityScope found") with(sharedTransitionScope) { - val targetRoundedPolygon = SnackPolygons.snackDetailPolygon - val restingRoundedPolygon = SnackPolygons.pillIntermediatePolygon + val targetRoundedPolygon = SnackPolygons.snackItemPolygon + val restingRoundedPolygon = SnackPolygons.snackDetailPolygon val morph = remember { Morph(restingRoundedPolygon, targetRoundedPolygon) } diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/SharedBoundsMorph.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/SharedBoundsMorph.kt index 6261889fe2..d32d7b20a1 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/SharedBoundsMorph.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/SharedBoundsMorph.kt @@ -133,19 +133,15 @@ fun Morph.asShape(progress: Float): Shape = GenericShape { size: Size, _ -> } object SnackPolygons { - val pillIntermediatePolygon = RoundedPolygon.pill(height = 1.85f).normalized() + val snackDetailPolygon = RoundedPolygon.pill(height = 1.85f).normalized() - val snackItemPolygon = RoundedPolygon.rectangle( - perVertexRounding = listOf( - CornerRounding(0.5f), CornerRounding(0.5f), - CornerRounding(0.5f), CornerRounding(0.5f), - ), - ).normalized() - - val snackDetailPolygon = RoundedPolygon.rectangle( + val snackItemPolygon = RoundedPolygon.rectangle( perVertexRounding = listOf( CornerRounding(0.25f), CornerRounding(0.25f), CornerRounding(0.25f), CornerRounding(0.25f), ), ).normalized() + + val snackItemPolygonRounded = RoundedPolygon.pill(height = 1.80f).normalized() + } From edaeacef991710790ce2d7c70e45806585a79e83 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Wed, 25 Mar 2026 21:14:59 +0000 Subject: [PATCH 24/33] Disable androidx startup initialization as this is just a small sample --- Jetsnack/app/build.gradle.kts | 1 + Jetsnack/app/src/main/AndroidManifest.xml | 4 ++++ Jetsnack/gradle/libs.versions.toml | 3 +++ 3 files changed, 8 insertions(+) diff --git a/Jetsnack/app/build.gradle.kts b/Jetsnack/app/build.gradle.kts index 74cd68447a..da2eae6c3c 100644 --- a/Jetsnack/app/build.gradle.kts +++ b/Jetsnack/app/build.gradle.kts @@ -147,4 +147,5 @@ dependencies { implementation(libs.androidx.glance.appwidget) implementation(libs.androidx.glance.preview) + implementation(libs.androidx.startup) } diff --git a/Jetsnack/app/src/main/AndroidManifest.xml b/Jetsnack/app/src/main/AndroidManifest.xml index 73c8131195..9854a96115 100644 --- a/Jetsnack/app/src/main/AndroidManifest.xml +++ b/Jetsnack/app/src/main/AndroidManifest.xml @@ -25,6 +25,10 @@ android:theme="@style/Theme.Jetsnack" android:enableOnBackInvokedCallback="true" tools:targetApi="33"> + Date: Wed, 25 Mar 2026 21:28:11 +0000 Subject: [PATCH 25/33] Media query for sizing --- .../example/jetsnack/ui/home/search/Categories.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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 cc1fbbcd82..9ba7830068 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 @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalMediaQueryApi::class) + package com.example.jetsnack.ui.home.search import android.content.res.Configuration @@ -44,6 +46,8 @@ import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalMediaQueryApi +import androidx.compose.ui.LocalUiMediaScope import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow @@ -53,6 +57,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.VerticalAlignmentLine +import androidx.compose.ui.mediaQuery import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp @@ -69,8 +74,14 @@ import kotlin.math.max @Composable fun SearchCategories(categories: List) { + val itemSize = if (mediaQuery { windowWidth > 600.dp }) { + 250.dp + } else { + 150.dp + } + LazyVerticalGrid( - columns = GridCells.Adaptive(150.dp), + columns = GridCells.Adaptive(itemSize), contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp), ) { categories.forEachIndexed { index, collection -> From 25a11ab51462912ff191ba68fe4bf35fe6c40c9e Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Thu, 26 Mar 2026 14:05:56 +0000 Subject: [PATCH 26/33] Run spotless --- .../example/jetsnack/model/SnackCollection.kt | 2 +- .../com/example/jetsnack/ui/MainActivity.kt | 2 +- .../example/jetsnack/ui/components/Button.kt | 1 - .../example/jetsnack/ui/components/Card.kt | 12 +++-- .../example/jetsnack/ui/components/Snacks.kt | 7 +-- .../java/com/example/jetsnack/ui/home/Feed.kt | 2 +- .../java/com/example/jetsnack/ui/home/Home.kt | 8 ++-- .../com/example/jetsnack/ui/home/cart/Cart.kt | 18 +++---- .../jetsnack/ui/home/search/Categories.kt | 47 ++++++++++--------- .../jetsnack/ui/snackdetail/SnackDetail.kt | 2 +- .../com/example/jetsnack/ui/theme/Color.kt | 1 - .../com/example/jetsnack/ui/theme/Styles.kt | 11 +++-- .../com/example/jetsnack/ui/theme/Type.kt | 1 - .../jetsnack/ui/utils/PreviewWrapper.kt | 5 -- .../jetsnack/ui/utils/SharedBoundsMorph.kt | 3 +- 15 files changed, 56 insertions(+), 66 deletions(-) diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt index aa9bfeae6d..265736230c 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt @@ -61,7 +61,7 @@ private val popular = SnackCollection( private val wfhFavs = tastyTreats.copy( id = Random.nextLong(), name = "WFH favourites", - type = CollectionType.Card + type = CollectionType.Card, ) private val newlyAdded = popular.copy( diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/MainActivity.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/MainActivity.kt index b6cb1592ee..a6622c1af5 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/MainActivity.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/MainActivity.kt @@ -52,7 +52,7 @@ class MainActivity : ComponentActivity() { val installedProviders = getSystemService(AppWidgetManager::class.java).installedProviders val providerInfo = installedProviders.firstOrNull { it.provider.className == - receiver.qualifiedName + receiver.qualifiedName } providerInfo?.generatedPreviewCategories.takeIf { it == 0 }?.let { // Set previews if this provider if unset 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 6569c249b6..b386c1eeaa 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 @@ -234,7 +234,6 @@ private fun ButtonPreviewPressedHovered() { } } - @Preview @Composable private fun ButtonDesktopPreview() { 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 28a4707d08..4e0580b798 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 @@ -35,11 +35,13 @@ import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.theme.JetsnackTheme @Composable -fun JetsnackCard(modifier: Modifier = Modifier, - style: Style = Style, - interactionSource: InteractionSource = remember { MutableInteractionSource() }, - styleState: StyleState = rememberUpdatedStyleState(interactionSource), - content: @Composable () -> Unit) { +fun JetsnackCard( + modifier: Modifier = Modifier, + style: Style = Style, + interactionSource: InteractionSource = remember { MutableInteractionSource() }, + styleState: StyleState = rememberUpdatedStyleState(interactionSource), + content: @Composable () -> Unit, +) { Surface( modifier = modifier, style = JetsnackTheme.styles.cardStyle then style, 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 beba8834b7..d8c27d0c08 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 @@ -171,11 +171,7 @@ private val plainCardStyle = Style { } @Composable -fun SnackCollection( - snackCollection: SnackCollection, - onSnackClick: (Long, String) -> Unit, - modifier: Modifier = Modifier, -) { +fun SnackCollection(snackCollection: SnackCollection, onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier) { Column(modifier = modifier) { Row( verticalAlignment = Alignment.CenterVertically, @@ -542,7 +538,6 @@ fun SnackCardPreviewPlain() { } } - @Composable fun JetsnackPreviewWrapper(content: @Composable () -> Unit) { JetsnackTheme { 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 84a7311f73..3b10fd85a9 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 @@ -132,7 +132,7 @@ private fun SnackCollectionList( SnackCollection( snackCollection = snackCollection, - onSnackClick = onSnackClick + onSnackClick = onSnackClick, ) } } 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 de7e106e2c..5d2a878198 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 @@ -94,21 +94,21 @@ fun NavGraphBuilder.composableWithCompositionLocal( arguments: List = emptyList(), deepLinks: List = emptyList(), enterTransition: ( - @JvmSuppressWildcards AnimatedContentTransitionScope.() -> EnterTransition? + @JvmSuppressWildcards AnimatedContentTransitionScope.() -> EnterTransition? )? = { fadeIn(nonSpatialExpressiveSpring()) }, exitTransition: ( - @JvmSuppressWildcards AnimatedContentTransitionScope.() -> ExitTransition? + @JvmSuppressWildcards AnimatedContentTransitionScope.() -> ExitTransition? )? = { fadeOut(nonSpatialExpressiveSpring()) }, popEnterTransition: ( - @JvmSuppressWildcards AnimatedContentTransitionScope.() -> EnterTransition? + @JvmSuppressWildcards AnimatedContentTransitionScope.() -> EnterTransition? )? = enterTransition, popExitTransition: ( - @JvmSuppressWildcards AnimatedContentTransitionScope.() -> ExitTransition? + @JvmSuppressWildcards AnimatedContentTransitionScope.() -> ExitTransition? )? = exitTransition, content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit, 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 96d5e2d121..bf2f04cd1c 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 @@ -218,7 +218,7 @@ private fun CartContent( placementSpec = itemPlacementSpec, ), snackCollection = inspiredByCart, - onSnackClick = onSnackClick + onSnackClick = onSnackClick, ) Spacer(Modifier.height(56.dp)) } @@ -506,14 +506,14 @@ private fun CheckoutBar(modifier: Modifier = Modifier) { private fun CartPreview() { JetsnackThemeWrapper { UiMediaScopeWrapper { - Cart( - orderLines = SnackRepo.getCart(), - removeSnack = {}, - increaseItemCount = {}, - decreaseItemCount = {}, - inspiredByCart = SnackRepo.getInspiredByCart(), - onSnackClick = { _, _ -> }, - ) + Cart( + orderLines = SnackRepo.getCart(), + removeSnack = {}, + increaseItemCount = {}, + decreaseItemCount = {}, + inspiredByCart = SnackRepo.getInspiredByCart(), + onSnackClick = { _, _ -> }, + ) } } } 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 9ba7830068..85eefa5595 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 @@ -141,29 +141,30 @@ private fun SearchCategory(category: SearchCategory, modifier: Modifier = Modifi .clickable( interactionSource = interactionSource, indication = ripple(), - ) { /* todo */ } ){ - Text( - text = category.name, - style = { - textStyleWithFontFamilyFix(typography.titleSmall) - contentColor(colors.textPrimary) - }, - modifier = Modifier - .padding(4.dp) - .padding(start = 8.dp) - .weight(1f, fill = true) - ) - SnackImage( - imageRes = category.imageRes, - contentDescription = null, - style = { - shape(RoundedCornerShape(topStartPercent = 48)) - }, - modifier = Modifier.fillMaxSize() - .weight(1f) - .defaultMinSize(minWidth = MinImageSize), - ) - } + ) { /* todo */ }, + ) { + Text( + text = category.name, + style = { + textStyleWithFontFamilyFix(typography.titleSmall) + contentColor(colors.textPrimary) + }, + modifier = Modifier + .padding(4.dp) + .padding(start = 8.dp) + .weight(1f, fill = true), + ) + SnackImage( + imageRes = category.imageRes, + contentDescription = null, + style = { + shape(RoundedCornerShape(topStartPercent = 48)) + }, + modifier = Modifier.fillMaxSize() + .weight(1f) + .defaultMinSize(minWidth = MinImageSize), + ) + } } @Preview("default") 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 d15a70ebbe..3e8f4244a0 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 @@ -559,7 +559,7 @@ private fun CartBottomBar(modifier: Modifier = Modifier) { ), ) { it } + fadeIn(tween(300, delayMillis = 300)), exit = slideOutVertically(tween(50)) { it } + - fadeOut(tween(50)), + fadeOut(tween(50)), ), ) { Column { 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 21b2ef6b37..28e3cefb47 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 @@ -18,7 +18,6 @@ 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 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 index e354e620a3..e8697035d7 100644 --- 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 @@ -35,7 +35,6 @@ import androidx.compose.runtime.Immutable import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.LocalUiMediaScope import androidx.compose.ui.UiMediaScope -import androidx.compose.ui.UiMediaScope.ViewingDistance import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush @@ -49,7 +48,6 @@ import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.mediaQuery import androidx.compose.ui.unit.DpOffset -import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix @@ -110,8 +108,11 @@ data class Styles( ) contentColor(colors.textSecondary) minWidth(58.dp) - if (mediaQuery { pointerPrecision == UiMediaScope.PointerPrecision.Fine - && keyboardKind == UiMediaScope.KeyboardKind.Physical }) { + if (mediaQuery { + pointerPrecision == UiMediaScope.PointerPrecision.Fine && + keyboardKind == UiMediaScope.KeyboardKind.Physical + } + ) { contentPaddingVertical(4.dp) contentPaddingHorizontal(8.dp) shape(shapes.medium) @@ -266,7 +267,7 @@ enum class LoadingState { val loadingStateKey = StyleStateKey(LoadingState.Loaded) -// Extension Function on MutableStyleState to query and set the current playState +// Extension Function on MutableStyleState to query and set the current loadingState var MutableStyleState.loadingState get() = this[loadingStateKey] set(value) { diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Type.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Type.kt index d70bc903ad..0fabf0b516 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Type.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Type.kt @@ -18,7 +18,6 @@ package com.example.jetsnack.ui.theme import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.googlefonts.Font diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt index 4589c6bfb5..b751b6f028 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/PreviewWrapper.kt @@ -28,14 +28,9 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.LocalUiMediaScope import androidx.compose.ui.UiMediaScope -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.tooling.preview.PreviewWrapper -import com.example.jetsnack.model.SnackRepo import com.example.jetsnack.ui.LocalNavAnimatedVisibilityScope import com.example.jetsnack.ui.LocalSharedTransitionScope -import com.example.jetsnack.ui.home.cart.Cart import com.example.jetsnack.ui.theme.JetsnackTheme @Composable diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/SharedBoundsMorph.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/SharedBoundsMorph.kt index d32d7b20a1..f64f47207d 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/SharedBoundsMorph.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/SharedBoundsMorph.kt @@ -135,7 +135,7 @@ fun Morph.asShape(progress: Float): Shape = GenericShape { size: Size, _ -> object SnackPolygons { val snackDetailPolygon = RoundedPolygon.pill(height = 1.85f).normalized() - val snackItemPolygon = RoundedPolygon.rectangle( + val snackItemPolygon = RoundedPolygon.rectangle( perVertexRounding = listOf( CornerRounding(0.25f), CornerRounding(0.25f), CornerRounding(0.25f), CornerRounding(0.25f), @@ -143,5 +143,4 @@ object SnackPolygons { ).normalized() val snackItemPolygonRounded = RoundedPolygon.pill(height = 1.80f).normalized() - } From c52e8d6ccb41712b70198585ea48c9f1275a4450 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Thu, 26 Mar 2026 14:24:49 +0000 Subject: [PATCH 27/33] Fix so that touch events are not propagated through --- .../com/example/jetsnack/ui/components/Filters.kt | 5 +++-- .../com/example/jetsnack/ui/components/Snacks.kt | 11 ++++++++++- .../java/com/example/jetsnack/ui/home/cart/Cart.kt | 13 ++++++++++--- 3 files changed, 23 insertions(+), 6 deletions(-) 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 704a628a26..df4da6a3ff 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 @@ -54,6 +54,7 @@ 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 +import com.example.jetsnack.ui.utils.JetsnackThemeWrapper @Composable fun FilterBar( @@ -150,7 +151,7 @@ fun FilterChip(filter: Filter, modifier: Modifier = Modifier, style: Style = Sty @Preview("large font", fontScale = 2f) @Composable private fun FilterDisabledPreview() { - JetsnackTheme { + JetsnackThemeWrapper { FilterChip(Filter(name = "Demo", enabled = false), Modifier.padding(4.dp)) } } @@ -160,7 +161,7 @@ private fun FilterDisabledPreview() { @Preview("large font", fontScale = 2f) @Composable private fun FilterEnabledPreview() { - JetsnackTheme { + JetsnackThemeWrapper { FilterChip(Filter(name = "Demo", enabled = true)) } } 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 d8c27d0c08..91f86a4f87 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 @@ -110,7 +110,6 @@ import com.example.jetsnack.ui.utils.formatPrice import com.example.jetsnack.ui.utils.sharedBoundsRevealWithShapeMorph private val HighlightCardWidth = 170.dp - private val highlightGlowCardStyle = Style { background(colors.brandLight) border(0.dp, colors.brandLight) @@ -121,6 +120,11 @@ private val highlightGlowCardStyle = Style { innerShadow(Shadow(offset = DpOffset((-6).dp, (-2).dp), radius = 8.dp, color = colors.brand.copy(alpha = 0.5f))) } } + pressed { + animate { + scale(1.05f) + } + } focused { animate { scale(1.05f) @@ -168,6 +172,11 @@ private val plainCardStyle = Style { scale(1.05f) } } + pressed { + animate { + scale(1.05f) + } + } } @Composable 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 bf2f04cd1c..03f1a33b41 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 @@ -23,6 +23,7 @@ import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -469,9 +470,15 @@ fun SummaryItem(subtotal: Long, shippingCosts: Long, modifier: Modifier = Modifi @Composable private fun CheckoutBar(modifier: Modifier = Modifier) { Column( - modifier.background( - JetsnackTheme.colors.uiBackground.copy(alpha = AlphaNearOpaque), - ), + modifier + .background( + JetsnackTheme.colors.uiBackground.copy(alpha = AlphaNearOpaque), + ) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { } + ), ) { JetsnackDivider() From cae3dc289ddedaa9583f9b088a80567450a0f05a Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Thu, 26 Mar 2026 14:55:06 +0000 Subject: [PATCH 28/33] Improve search size --- .../main/java/com/example/jetsnack/ui/home/search/Search.kt | 3 +++ 1 file changed, 3 insertions(+) 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 4cc644d22b..23d6559c3a 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 @@ -59,9 +59,11 @@ import com.example.jetsnack.model.SnackRepo import com.example.jetsnack.ui.components.JetsnackDivider 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.shapes +import com.example.jetsnack.ui.theme.typography @Composable fun Search(onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier, state: SearchState = rememberSearchState()) { @@ -242,6 +244,7 @@ private fun SearchHint() { Text( text = stringResource(R.string.search_jetsnack), style = { + textStyleWithFontFamilyFix(typography.bodyLarge) contentColor(colors.textHelp) }, ) From 37df104a0ea903a806aeded0f9d65f9aafcc2c23 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Thu, 26 Mar 2026 18:29:30 +0000 Subject: [PATCH 29/33] Refactor Cart swipe-to-dismiss and enhance UI component interactions This update improves the swipe-to-dismiss behavior in the Cart and refactors base components to better support layout alignment and interaction testing. --- .../example/jetsnack/ui/components/Card.kt | 3 +- .../example/jetsnack/ui/components/Filters.kt | 98 ++++++++++++++++++- .../example/jetsnack/ui/components/Surface.kt | 5 +- .../com/example/jetsnack/ui/home/cart/Cart.kt | 92 ++++++++++------- .../jetsnack/ui/home/cart/SwipeDismissItem.kt | 8 +- 5 files changed, 160 insertions(+), 46 deletions(-) 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 4e0580b798..a50e321c32 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 @@ -21,6 +21,7 @@ package com.example.jetsnack.ui.components import android.content.res.Configuration import androidx.compose.foundation.interaction.InteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.padding import androidx.compose.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.foundation.style.Style @@ -40,7 +41,7 @@ fun JetsnackCard( style: Style = Style, interactionSource: InteractionSource = remember { MutableInteractionSource() }, styleState: StyleState = rememberUpdatedStyleState(interactionSource), - content: @Composable () -> Unit, + content: @Composable BoxScope.() -> Unit, ) { Surface( modifier = modifier, 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 df4da6a3ff..36aff1055a 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,16 +14,25 @@ * limitations under the License. */ -@file:OptIn(ExperimentalSharedTransitionApi::class, ExperimentalFoundationStyleApi::class) +@file:OptIn( + ExperimentalSharedTransitionApi::class, + ExperimentalFoundationStyleApi::class, + ExperimentalMediaQueryApi::class +) package com.example.jetsnack.ui.components import android.content.res.Configuration +import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope +import androidx.compose.foundation.interaction.FocusInteraction +import androidx.compose.foundation.interaction.HoverInteraction import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding @@ -39,9 +48,12 @@ import androidx.compose.foundation.style.then import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.UiMediaScope +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -55,6 +67,7 @@ import com.example.jetsnack.ui.theme.colors import com.example.jetsnack.ui.theme.shapes import com.example.jetsnack.ui.theme.typography import com.example.jetsnack.ui.utils.JetsnackThemeWrapper +import com.example.jetsnack.ui.utils.UiMediaScopeWrapper @Composable fun FilterBar( @@ -110,10 +123,14 @@ fun FilterBar( } @Composable -fun FilterChip(filter: Filter, modifier: Modifier = Modifier, style: Style = Style) { +fun FilterChip( + filter: Filter, + modifier: Modifier = Modifier, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + style: Style = Style, +) { val (selected, setSelected) = filter.enabled - val interactionSource = remember { MutableInteractionSource() } val styleState = rememberUpdatedStyleState( interactionSource, { @@ -165,3 +182,78 @@ private fun FilterEnabledPreview() { FilterChip(Filter(name = "Demo", enabled = true)) } } + +@Preview("hovered focused") +@Preview("dark theme hovered focused", uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun FilterPreviewHoveredFocused() { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, pointerPrecision = UiMediaScope.PointerPrecision.Blunt) { + val interactionSource = remember { MutableInteractionSource() } + LaunchedEffect(interactionSource) { + interactionSource.emit(HoverInteraction.Enter()) + interactionSource.emit(FocusInteraction.Focus()) + } + JetsnackThemeWrapper { + FilterChip( + filter = Filter(name = "Demo"), + interactionSource = interactionSource, + ) + } + } +} + +@Preview("pressed focused") +@Preview("dark theme pressed focused", uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun FilterPreviewPressedFocused() { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, pointerPrecision = UiMediaScope.PointerPrecision.Blunt) { + val interactionSource = remember { MutableInteractionSource() } + LaunchedEffect(interactionSource) { + interactionSource.emit(FocusInteraction.Focus()) + interactionSource.emit(PressInteraction.Press(Offset.Zero)) + } + JetsnackThemeWrapper { + FilterChip( + filter = Filter(name = "Demo"), + interactionSource = interactionSource, + ) + } + } +} + +@Preview("pressed hovered") +@Preview("dark theme pressed hovered", uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun FilterPreviewPressedHovered() { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, pointerPrecision = UiMediaScope.PointerPrecision.Blunt) { + val interactionSource = remember { MutableInteractionSource() } + LaunchedEffect(interactionSource) { + interactionSource.emit(PressInteraction.Press(Offset.Zero)) + interactionSource.emit(HoverInteraction.Enter()) + } + JetsnackThemeWrapper { + FilterChip( + filter = Filter(name = "Demo"), + interactionSource = interactionSource, + ) + } + } +} + +@Preview("pressed") +@Preview("dark theme pressed", uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun FilterPreviewPressed() { + UiMediaScopeWrapper(keyboardKind = UiMediaScope.KeyboardKind.Virtual, pointerPrecision = UiMediaScope.PointerPrecision.Blunt) { + val interactionSource = remember { MutableInteractionSource() } + LaunchedEffect(interactionSource) { + interactionSource.emit(PressInteraction.Press(Offset.Zero)) + } + JetsnackThemeWrapper { + FilterChip( + filter = Filter(name = "Demo"), + interactionSource = interactionSource, + ) + } + } +} 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 44549321d2..7c0533da90 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 @@ -19,6 +19,7 @@ package com.example.jetsnack.ui.components import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.style.ExperimentalFoundationStyleApi import androidx.compose.foundation.style.Style import androidx.compose.foundation.style.StyleState @@ -36,15 +37,13 @@ import com.example.jetsnack.ui.theme.JetsnackTheme fun Surface( modifier: Modifier = Modifier, style: Style = Style, - // todo confirm patten is acceptable styleState: StyleState = rememberUpdatedStyleState(null), - content: @Composable () -> Unit, + content: @Composable BoxScope.() -> Unit, ) { Box( modifier = modifier .styleable(styleState, JetsnackTheme.styles.surfaceStyle, style), ) { - // todo double check CompositionLocalProvider(LocalContentColor provides contentColor, content = content) content() } } 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 03f1a33b41..80c4108c47 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 @@ -19,6 +19,7 @@ package com.example.jetsnack.ui.home.cart import android.content.res.Configuration.UI_MODE_NIGHT_YES +import android.util.Log import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background @@ -31,6 +32,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.add +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -49,7 +51,10 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Surface +import androidx.compose.material3.SwipeToDismissBoxState +import androidx.compose.material3.rememberSwipeToDismissBoxState import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -90,7 +95,9 @@ import com.example.jetsnack.ui.theme.typography import com.example.jetsnack.ui.utils.JetsnackThemeWrapper import com.example.jetsnack.ui.utils.UiMediaScopeWrapper import com.example.jetsnack.ui.utils.formatPrice +import kotlin.math.abs import kotlin.math.roundToInt +import androidx.compose.foundation.layout.BoxWithConstraints @Composable fun Cart( @@ -124,19 +131,17 @@ fun Cart( com.example.jetsnack.ui.components.Surface( modifier = modifier.fillMaxSize(), ) { - Box(modifier = Modifier.fillMaxSize()) { - CartContent( - orderLines = orderLines, - removeSnack = removeSnack, - increaseItemCount = increaseItemCount, - decreaseItemCount = decreaseItemCount, - inspiredByCart = inspiredByCart, - onSnackClick = onSnackClick, - modifier = Modifier.align(Alignment.TopCenter), - ) - DestinationBar(modifier = Modifier.align(Alignment.TopCenter)) - CheckoutBar(modifier = Modifier.align(Alignment.BottomCenter)) - } + CartContent( + orderLines = orderLines, + removeSnack = removeSnack, + increaseItemCount = increaseItemCount, + decreaseItemCount = decreaseItemCount, + inspiredByCart = inspiredByCart, + onSnackClick = onSnackClick, + modifier = Modifier.align(Alignment.TopCenter), + ) + DestinationBar(modifier = Modifier.align(Alignment.TopCenter)) + CheckoutBar(modifier = Modifier.align(Alignment.BottomCenter)) } } @@ -181,14 +186,16 @@ private fun CartContent( ) } items(orderLines, key = { it.snack.id }) { orderLine -> + val swipeDismissState = rememberSwipeToDismissBoxState() SwipeDismissItem( + dismissState = swipeDismissState, modifier = Modifier.animateItem( fadeInSpec = itemAnimationSpecFade, fadeOutSpec = itemAnimationSpecFade, placementSpec = itemPlacementSpec, ), - background = { progress -> - SwipeDismissItemBackground(progress) + background = { + SwipeDismissItemBackground(swipeDismissState) }, ) { CartItem( @@ -227,27 +234,42 @@ private fun CartContent( } @Composable -private fun SwipeDismissItemBackground(progress: Float) { - Column( +private fun SwipeDismissItemBackground(swipeDismissState: SwipeToDismissBoxState) { + BoxWithConstraints( modifier = Modifier .background(JetsnackTheme.colors.uiBackground) - .fillMaxWidth(), - horizontalAlignment = Alignment.End, - verticalArrangement = Arrangement.Center, + .fillMaxSize(), ) { - // Set 4.dp padding only if progress is less than halfway - val padding: Dp by animateDpAsState( - if (progress < 0.5f) 4.dp else 0.dp, label = "padding", - ) - Box( - Modifier - .fillMaxWidth(progress), + val progress = remember(swipeDismissState, constraints.maxWidth) { + derivedStateOf { + if (constraints.maxWidth > 0) { + val offset = try { + swipeDismissState.requireOffset() + } catch ( _ : IllegalStateException) { + 0f + } + (abs(offset) / constraints.maxWidth).coerceIn(0f, 1f) + } else { + 0f + } + } + }.value + + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.End, + verticalArrangement = Arrangement.Center, ) { + // Set 4.dp padding only if progress is less than halfway + val padding: Dp by animateDpAsState( + if (progress < 0.5f) 4.dp else 0.dp, label = "padding", + ) + Surface( modifier = Modifier - .padding(padding) - .fillMaxWidth() - .align(Alignment.Center), + .fillMaxHeight() + .fillMaxWidth(progress) + .padding(padding), shape = RoundedCornerShape(percent = ((1 - progress) * 100).roundToInt()), color = JetsnackTheme.colors.error, ) { @@ -256,7 +278,7 @@ private fun SwipeDismissItemBackground(progress: Float) { contentAlignment = Alignment.Center, ) { // Icon must be visible while in this width range - if (progress in 0.125f..0.475f) { + if (progress in 0.125f..0.4f) { // Icon alpha decreases as it is about to disappear val iconAlpha: Float by animateFloatAsState( if (progress > 0.4f) 0.5f else 1f, label = "icon alpha", @@ -265,7 +287,7 @@ private fun SwipeDismissItemBackground(progress: Float) { Icon( painter = painterResource(id = R.drawable.ic_delete_forever), modifier = Modifier - .size(32.dp) + .size(28.dp) .graphicsLayer(alpha = iconAlpha), tint = JetsnackTheme.colors.uiBackground, contentDescription = null, @@ -274,9 +296,9 @@ private fun SwipeDismissItemBackground(progress: Float) { /*Text opacity increases as the text is supposed to appear in the screen*/ val textAlpha by animateFloatAsState( - if (progress > 0.5f) 1f else 0.5f, label = "text alpha", + if (progress > 0.4f) 1f else 0.5f, label = "text alpha", ) - if (progress > 0.5f) { + if (progress > 0.40f) { Text( text = stringResource(id = R.string.remove_item), style = { @@ -477,7 +499,7 @@ private fun CheckoutBar(modifier: Modifier = Modifier) { .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null, - onClick = { } + onClick = { }, ), ) { diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/SwipeDismissItem.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/SwipeDismissItem.kt index 3e806fdc50..74d463a929 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/SwipeDismissItem.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/SwipeDismissItem.kt @@ -23,6 +23,7 @@ import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SwipeToDismissBox +import androidx.compose.material3.SwipeToDismissBoxState import androidx.compose.material3.SwipeToDismissBoxValue import androidx.compose.material3.rememberSwipeToDismissBoxState import androidx.compose.runtime.Composable @@ -35,13 +36,12 @@ import androidx.compose.ui.Modifier @Composable fun SwipeDismissItem( modifier: Modifier = Modifier, + dismissState: SwipeToDismissBoxState = rememberSwipeToDismissBoxState(), enter: EnterTransition = expandVertically(), exit: ExitTransition = shrinkVertically(), - background: @Composable (progress: Float) -> Unit, + background: @Composable () -> Unit, content: @Composable (isDismissed: Boolean) -> Unit, ) { - // Hold the current state from the Swipe to Dismiss composable - val dismissState = rememberSwipeToDismissBoxState() // Boolean value used for hiding the item if the current state is dismissed val isDismissed = dismissState.currentValue == SwipeToDismissBoxValue.EndToStart @@ -55,7 +55,7 @@ fun SwipeDismissItem( modifier = modifier, state = dismissState, enableDismissFromStartToEnd = false, - backgroundContent = { background(dismissState.progress) }, + backgroundContent = { background() }, content = { content(isDismissed) }, ) } From 85393f5041ffd431718786ca4a946dce396ecf8a Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 27 Mar 2026 12:18:25 +0000 Subject: [PATCH 30/33] Refactor EllipticalGradient.kt out into separate file --- .../com/example/jetsnack/ui/theme/Styles.kt | 59 ++----------------- .../jetsnack/ui/utils/EllipticalGradient.kt | 53 +++++++++++++++++ 2 files changed, 58 insertions(+), 54 deletions(-) create mode 100644 Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/EllipticalGradient.kt 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 index e8697035d7..18767aad36 100644 --- 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 @@ -33,72 +33,24 @@ import androidx.compose.foundation.style.selected import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Immutable import androidx.compose.ui.ExperimentalMediaQueryApi -import androidx.compose.ui.LocalUiMediaScope import androidx.compose.ui.UiMediaScope -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Matrix -import androidx.compose.ui.graphics.RadialGradientShader import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.graphics.Shader -import androidx.compose.ui.graphics.ShaderBrush import androidx.compose.ui.graphics.TileMode +import com.example.jetsnack.ui.utils.ellipticalGradient import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.mediaQuery import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix -/** - * Creates an elliptical radial gradient brush that emulates the CSS radial-gradient spec. - * - * @param colors The colors to be distributed along the gradient. - * @param stops Optional color stops (0.0 to 1.0). - * @param radiusXPercent The horizontal radius as a percentage of the width (1.0 = 100%). - * @param radiusYPercent The vertical radius as a percentage of the height (1.0 = 100%). - * @param centerXPercent The horizontal center position as a percentage of the width. - * @param centerYPercent The vertical center position as a percentage of the height. - * @param tileMode The tile mode for the gradient. - */ -fun ellipticalGradient( - colors: List, - stops: List? = null, - radiusXPercent: Float, - radiusYPercent: Float, - centerXPercent: Float = 0.5f, - centerYPercent: Float = 0.5f, - tileMode: TileMode = TileMode.Clamp, -): ShaderBrush = object : ShaderBrush() { - override fun createShader(size: Size): Shader { - val rX = size.width * radiusXPercent - val rY = size.height * radiusYPercent - val cX = size.width * centerXPercent - val cY = size.height * centerYPercent - - this.transform = Matrix().apply { - reset() - translate(cX, cY) - scale(rX, rY) - } - - return RadialGradientShader( - colors = colors, - colorStops = stops, - center = Offset.Zero, - radius = 1f, - tileMode = tileMode, - ) - } -} - @Immutable data class Styles( val buttonStyle: Style = Style { shape(shapes.small) background( - ellipticalGradient( + Brush.ellipticalGradient( colors = colors.interactivePrimary, radiusXPercent = 1.3f, radiusYPercent = 0.7f, @@ -110,7 +62,7 @@ data class Styles( minWidth(58.dp) if (mediaQuery { pointerPrecision == UiMediaScope.PointerPrecision.Fine && - keyboardKind == UiMediaScope.KeyboardKind.Physical + keyboardKind == UiMediaScope.KeyboardKind.Physical } ) { contentPaddingVertical(4.dp) @@ -167,7 +119,7 @@ data class Styles( loading { animate { background( - ellipticalGradient( + Brush.ellipticalGradient( colors = colors.interactivePrimary.reversed(), radiusXPercent = 1.3f, radiusYPercent = 0.7f, @@ -227,7 +179,7 @@ data class Styles( textStyleWithFontFamilyFix(typography.labelSmall) pressed { animate { - val gradient = ellipticalGradient( + val gradient = Brush.ellipticalGradient( colors = colors.interactivePrimary, radiusXPercent = 1.3f, radiusYPercent = 0.7232f, @@ -267,7 +219,6 @@ enum class LoadingState { val loadingStateKey = StyleStateKey(LoadingState.Loaded) -// Extension Function on MutableStyleState to query and set the current loadingState var MutableStyleState.loadingState get() = this[loadingStateKey] set(value) { diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/EllipticalGradient.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/EllipticalGradient.kt new file mode 100644 index 0000000000..b136e7f7de --- /dev/null +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/EllipticalGradient.kt @@ -0,0 +1,53 @@ +package com.example.jetsnack.ui.utils + +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Matrix +import androidx.compose.ui.graphics.RadialGradientShader +import androidx.compose.ui.graphics.Shader +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.TileMode + +/** + * Creates an elliptical radial gradient brush that emulates the CSS radial-gradient spec. + * + * @param colors The colors to be distributed along the gradient. + * @param stops Optional color stops (0.0 to 1.0). + * @param radiusXPercent The horizontal radius as a percentage of the width (1.0 = 100%). + * @param radiusYPercent The vertical radius as a percentage of the height (1.0 = 100%). + * @param centerXPercent The horizontal center position as a percentage of the width. + * @param centerYPercent The vertical center position as a percentage of the height. + * @param tileMode The tile mode for the gradient. + */ +fun Brush.Companion.ellipticalGradient( + colors: List, + stops: List? = null, + radiusXPercent: Float, + radiusYPercent: Float, + centerXPercent: Float = 0.5f, + centerYPercent: Float = 0.5f, + tileMode: TileMode = TileMode.Clamp, +): ShaderBrush = object : ShaderBrush() { + override fun createShader(size: Size): Shader { + val rX = size.width * radiusXPercent + val rY = size.height * radiusYPercent + val cX = size.width * centerXPercent + val cY = size.height * centerYPercent + + this.transform = Matrix().apply { + reset() + translate(cX, cY) + scale(rX, rY) + } + + return RadialGradientShader( + colors = colors, + colorStops = stops, + center = Offset.Zero, + radius = 1f, + tileMode = tileMode, + ) + } +} \ No newline at end of file From 4deb059a871f9bfa1477828ca85a82a3bc0e0b4b Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 27 Mar 2026 12:57:37 +0000 Subject: [PATCH 31/33] Extract SnackCard styles --- .../example/jetsnack/ui/components/Snacks.kt | 86 ++----------------- .../com/example/jetsnack/ui/theme/Styles.kt | 54 +++++++++++- 2 files changed, 59 insertions(+), 81 deletions(-) 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 91f86a4f87..73a2e14385 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 @@ -57,9 +57,6 @@ 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.focused -import androidx.compose.foundation.style.hovered -import androidx.compose.foundation.style.pressed import androidx.compose.foundation.style.then import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon @@ -71,8 +68,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext @@ -82,7 +77,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.graphics.shapes.Morph import androidx.graphics.shapes.RoundedPolygon @@ -109,76 +103,11 @@ import com.example.jetsnack.ui.utils.asShape import com.example.jetsnack.ui.utils.formatPrice import com.example.jetsnack.ui.utils.sharedBoundsRevealWithShapeMorph -private val HighlightCardWidth = 170.dp -private val highlightGlowCardStyle = Style { - background(colors.brandLight) - border(0.dp, colors.brandLight) - hovered { - animate { - scale(1.05f) - dropShadow(Shadow(offset = DpOffset(0.dp, 2.dp), radius = 6.dp, color = colors.brand)) - innerShadow(Shadow(offset = DpOffset((-6).dp, (-2).dp), radius = 8.dp, color = colors.brand.copy(alpha = 0.5f))) - } - } - pressed { - animate { - scale(1.05f) - } - } - focused { - animate { - scale(1.05f) - dropShadow(Shadow(offset = DpOffset(0.dp, 2.dp), radius = 6.dp, color = colors.brand)) - innerShadow(Shadow(offset = DpOffset((-6).dp, (-2).dp), radius = 8.dp, color = colors.brand.copy(alpha = 0.5f))) - } - } -} -private val normalCardStyle = Style { - background(Color.Transparent) - width(100.dp) - contentPadding(2.dp) - textAlign(TextAlign.Center) - hovered { - animate { - scale(1.05f) - } - } - focused { - animate { - scale(1.05f) - } - } - pressed { - background(colors.uiFloated.copy(alpha = 0.5f)) - } -} private val normalTextStyle = Style { textStyleWithFontFamilyFix(typography.titleSmall) textAlign(TextAlign.Center) } -private val plainCardStyle = Style { - background(colors.cardHighlightBackground) - clip(true) - border(1.dp, colors.cardHighlightBorder) - textAlign(TextAlign.Center) - hovered { - animate { - scale(1.05f) - } - } - focused { - animate { - scale(1.05f) - } - } - pressed { - animate { - scale(1.05f) - } - } -} - @Composable fun SnackCollection(snackCollection: SnackCollection, onSnackClick: (Long, String) -> Unit, modifier: Modifier = Modifier) { Column(modifier = modifier) { @@ -226,26 +155,24 @@ fun SnackCollection(snackCollection: SnackCollection, onSnackClick: (Long, Strin onSnackClick = onSnackClick, showTagLine = false, imageShape = SnackPolygons.snackItemPolygonRounded, - style = normalCardStyle, + style = JetsnackTheme.styles.normalCardStyle, snackTextStyle = normalTextStyle, imageAspectRatio = 4f / 3f, ) - CollectionType.Highlight -> SnackItem( snackCollectionId = snackCollection.id, snack = snack, onSnackClick = onSnackClick, showAddButton = true, - style = highlightGlowCardStyle, + style = JetsnackTheme.styles.highlightGlowCardStyle, ) - CollectionType.Card -> SnackItem( snackCollectionId = snackCollection.id, snack = snack, onSnackClick = onSnackClick, - style = plainCardStyle, + style = JetsnackTheme.styles.plainCardStyle, imageAspectRatio = 16 / 9f, ) } @@ -284,7 +211,6 @@ private fun SnackItem( JetsnackCard( style = Style { shape(RoundedCornerShape(roundedCornerAnimation)) - width(HighlightCardWidth) } then style, interactionSource = interactionSource, modifier = modifier @@ -501,7 +427,7 @@ fun SnackCardPreviewCard() { SnackItem( snackCollectionId = 1, snack = snack, - style = normalCardStyle, + style = JetsnackTheme.styles.normalCardStyle, showTagLine = false, onSnackClick = { _, _ -> }, ) @@ -520,7 +446,7 @@ fun SnackCardPreviewHighlight() { SnackItem( snackCollectionId = 1, snack = snack, - style = highlightGlowCardStyle, + style = JetsnackTheme.styles.highlightGlowCardStyle, showAddButton = true, onSnackClick = { _, _ -> }, ) @@ -540,7 +466,7 @@ fun SnackCardPreviewPlain() { snackCollectionId = 1, snack = snack, showTagLine = false, - style = normalCardStyle, + style = JetsnackTheme.styles.normalCardStyle, onSnackClick = { _, _ -> }, ) } 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 index 18767aad36..34b1aea328 100644 --- 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 @@ -30,6 +30,7 @@ import androidx.compose.foundation.style.focused import androidx.compose.foundation.style.hovered import androidx.compose.foundation.style.pressed import androidx.compose.foundation.style.selected +import androidx.compose.foundation.style.then import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Immutable import androidx.compose.ui.ExperimentalMediaQueryApi @@ -38,12 +39,13 @@ 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 com.example.jetsnack.ui.utils.ellipticalGradient import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.mediaQuery +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix +import com.example.jetsnack.ui.utils.ellipticalGradient @Immutable data class Styles( @@ -209,6 +211,56 @@ data class Styles( contentColor(colors.textSecondary) clip(true) }, + val baseSnackCardStyle : Style = Style { + textAlign(TextAlign.Center) + width(170.dp) + + hovered { + animate { + scale(1.05f) + } + } + focused { + animate { + scale(1.05f) + } + } + pressed { + animate { + scale(1.05f) + } + } + }, + val highlightGlowCardStyle : Style = baseSnackCardStyle then Style { + background(colors.brandLight) + border(0.dp, colors.brandLight) + hovered { + animate { + dropShadow(Shadow(offset = DpOffset(0.dp, 2.dp), radius = 6.dp, color = colors.brand)) + innerShadow(Shadow(offset = DpOffset((-6).dp, (-2).dp), radius = 8.dp, color = colors.brand.copy(alpha = 0.5f))) + } + } + focused { + animate { + dropShadow(Shadow(offset = DpOffset(0.dp, 2.dp), radius = 6.dp, color = colors.brand)) + innerShadow(Shadow(offset = DpOffset((-6).dp, (-2).dp), radius = 8.dp, color = colors.brand.copy(alpha = 0.5f))) + } + } + }, + val normalCardStyle : Style = baseSnackCardStyle then Style { + background(Color.Transparent) + width(100.dp) + contentPadding(2.dp) + textAlign(TextAlign.Center) + pressed { + background(colors.uiFloated.copy(alpha = 0.5f)) + } + }, + val plainCardStyle : Style = baseSnackCardStyle then Style { + background(colors.cardHighlightBackground) + clip(true) + border(1.dp, colors.cardHighlightBorder) + } ) enum class LoadingState { From eee2b78430fd3c88ffb6153436eb31c3b24dddf5 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 27 Mar 2026 18:05:43 +0000 Subject: [PATCH 32/33] Add snack size changes on window size scale. --- .../java/com/example/jetsnack/ui/home/Home.kt | 3 +- .../com/example/jetsnack/ui/theme/Styles.kt | 37 ++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) 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 5d2a878198..4a922c53d3 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 @@ -38,6 +38,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape @@ -184,7 +185,7 @@ fun JetsnackBottomBar( background(color) contentColor(contentColor) if (mediaQuery { windowWidth > 600.dp }) { - contentPaddingHorizontal(200.dp) + contentPaddingHorizontal(100.dp) } }, ) { 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 index 34b1aea328..4dc5b7a108 100644 --- 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 @@ -19,12 +19,14 @@ 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.MutableStyleState import androidx.compose.foundation.style.Style import androidx.compose.foundation.style.StyleScope import androidx.compose.foundation.style.StyleStateKey import androidx.compose.foundation.style.disabled +import androidx.compose.foundation.style.fillHeight import androidx.compose.foundation.style.fillWidth import androidx.compose.foundation.style.focused import androidx.compose.foundation.style.hovered @@ -34,18 +36,21 @@ import androidx.compose.foundation.style.then import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Immutable import androidx.compose.ui.ExperimentalMediaQueryApi +import androidx.compose.ui.LocalUiMediaScope import androidx.compose.ui.UiMediaScope +import androidx.compose.ui.UiMediaScope.ViewingDistance 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 com.example.jetsnack.ui.utils.ellipticalGradient import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.mediaQuery import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import com.example.jetsnack.ui.components.textStyleWithFontFamilyFix -import com.example.jetsnack.ui.utils.ellipticalGradient @Immutable data class Styles( @@ -213,8 +218,17 @@ data class Styles( }, val baseSnackCardStyle : Style = Style { textAlign(TextAlign.Center) - width(170.dp) + // todo this animation doesn't seem to play nice + if (mediaQuery { windowWidth > 500.dp }) { + animate { + width(200.dp) + } + } else { + animate { + width(170.dp) + } + } hovered { animate { scale(1.05f) @@ -263,6 +277,25 @@ data class Styles( } ) +fun StyleScope.adaptiveFontSize(fontSize: TextUnit) { + var scaleFactor = when (LocalUiMediaScope.currentValue.viewingDistance) { + ViewingDistance.Near -> 1f + ViewingDistance.Medium -> 1.72f + ViewingDistance.Far -> 1.5f + else -> 1f + } + scaleFactor = when (LocalUiMediaScope.currentValue.pointerPrecision) { + UiMediaScope.PointerPrecision.Coarse -> scaleFactor * 1f + UiMediaScope.PointerPrecision.Blunt -> scaleFactor * 0.66f + UiMediaScope.PointerPrecision.Fine -> scaleFactor * 1f + UiMediaScope.PointerPrecision.None -> scaleFactor + else -> { + scaleFactor + } + } + fontSize(fontSize * scaleFactor) +} + enum class LoadingState { Loading, Loaded, From c6a01566c9ac25b6c409bf0ce000f836993d9156 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 30 Mar 2026 11:47:35 +0100 Subject: [PATCH 33/33] Remove gradients for pressed state --- .../main/java/com/example/jetsnack/ui/components/Button.kt | 1 + .../app/src/main/java/com/example/jetsnack/ui/theme/Styles.kt | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) 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 b386c1eeaa..16e4ac12f2 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 @@ -48,6 +48,7 @@ import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.theme.LoadingState import com.example.jetsnack.ui.theme.loadingState import com.example.jetsnack.ui.utils.UiMediaScopeWrapper +import androidx.compose.material3.Button @Composable fun Button( 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 index 4dc5b7a108..2fb4019393 100644 --- 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 @@ -102,7 +102,7 @@ data class Styles( } pressed { animate { - background(Brush.radialGradient(listOf(colors.brand, colors.brand))) + background(colors.brand) dropShadow(Shadow(color = colors.brand, offset = DpOffset(x = 0.dp, y = 0.dp), radius = 0.dp)) innerShadow(Shadow(color = colors.brand, offset = DpOffset(x = (0).dp, (0).dp), radius = 0.dp)) } @@ -116,7 +116,7 @@ data class Styles( // we don't want to combine these two // so set the properties to the same animate { - background(Brush.radialGradient(listOf(colors.brand, colors.brand))) + background(colors.brand) dropShadow(Shadow(color = colors.brand, offset = DpOffset(x = 0.dp, y = 0.dp), radius = 0.dp)) innerShadow(Shadow(color = colors.brand, offset = DpOffset(x = (0).dp, (0).dp), radius = 0.dp)) }