diff --git a/demo-app/src/main/java/io/opentelemetry/android/demo/clients/RecommendationService.kt b/demo-app/src/main/java/io/opentelemetry/android/demo/clients/RecommendationService.kt new file mode 100644 index 000000000..647f5c2da --- /dev/null +++ b/demo-app/src/main/java/io/opentelemetry/android/demo/clients/RecommendationService.kt @@ -0,0 +1,27 @@ +package io.opentelemetry.android.demo.clients + +import io.opentelemetry.android.demo.model.Product +import io.opentelemetry.android.demo.ui.shop.cart.CartViewModel + +class RecommendationService( + private val productCatalogClient: ProductCatalogClient, + private val cartViewModel: CartViewModel +) { + + fun getRecommendedProducts(currentProduct: Product, numberOfProducts: Int = 4): List { + return getAllNonCartProducts().filter { it.id != currentProduct.id } + .shuffled().take(numberOfProducts) + } + + fun getRecommendedProducts(numberOfProducts: Int = 4): List { + return getAllNonCartProducts().shuffled().take(numberOfProducts) + } + + private fun getAllNonCartProducts(): List{ + val allProducts = productCatalogClient.get() + val cartItems = cartViewModel.cartItems.value + + return allProducts.filter { product -> cartItems.none { it.product.id == product.id } } + } +} + diff --git a/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/AstronomyShopActivity.kt b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/AstronomyShopActivity.kt index 38b20949c..dd02b637a 100644 --- a/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/AstronomyShopActivity.kt +++ b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/AstronomyShopActivity.kt @@ -36,6 +36,7 @@ import io.opentelemetry.android.demo.ui.shop.cart.InfoScreen class AstronomyShopActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + title = "Astronomy Shop" setContent { AstronomyShopScreen() } @@ -84,15 +85,25 @@ fun AstronomyShopScreen() { } } composable(BottomNavItem.Cart.route) { - CartScreen(cartViewModel = cartViewModel) { astronomyShopNavController.navigateToCheckoutInfo() } + CartScreen(cartViewModel = cartViewModel, onCheckoutClick = {astronomyShopNavController.navigateToCheckoutInfo()}, onProductClick = { productId -> + astronomyShopNavController.navigateToProductDetail(productId) + }) } composable("${MainDestinations.PRODUCT_DETAIL_ROUTE}/{${MainDestinations.PRODUCT_ID_KEY}}") { backStackEntry -> val productId = backStackEntry.arguments?.getString(MainDestinations.PRODUCT_ID_KEY) val product = products.find { it.id == productId } - product?.let { ProductDetails(product = it, cartViewModel) } + product?.let { ProductDetails( + product = it, + cartViewModel, + upPress = {astronomyShopNavController.upPress()}, + onProductClick = { productId -> + astronomyShopNavController.navigateToProductDetail(productId) + } + ) + } } composable(MainDestinations.CHECKOUT_INFO_ROUTE) { - InfoScreen() + InfoScreen(upPress = {astronomyShopNavController.upPress()}) } } } diff --git a/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/cart/Cart.kt b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/cart/Cart.kt index 3dd400178..f5e18bacb 100644 --- a/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/cart/Cart.kt +++ b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/cart/Cart.kt @@ -8,70 +8,85 @@ import androidx.compose.ui.Modifier import java.util.Locale import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import io.opentelemetry.android.demo.model.Product import io.opentelemetry.android.demo.ui.shop.products.ProductCard import androidx.compose.ui.Alignment import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import io.opentelemetry.android.demo.clients.ProductCatalogClient +import io.opentelemetry.android.demo.clients.RecommendationService +import io.opentelemetry.android.demo.ui.shop.products.RecommendedSection @Composable -fun CartScreen(cartViewModel: CartViewModel = viewModel(), - onCheckoutClick: () -> Unit +fun CartScreen( + cartViewModel: CartViewModel = viewModel(), + onCheckoutClick: () -> Unit, + onProductClick: (String) -> Unit ) { + val context = LocalContext.current + val productsClient = ProductCatalogClient(context) + val recommendationService = remember { RecommendationService(productsClient, cartViewModel) } val cartItems by cartViewModel.cartItems.collectAsState() val isCartEmpty = cartItems.isEmpty() + val recommendedProducts = remember { recommendationService.getRecommendedProducts() } - Column( + + LazyColumn( modifier = Modifier .fillMaxSize() .padding(16.dp) ) { - Box( - modifier = Modifier.fillMaxWidth(), - contentAlignment = Alignment.TopEnd - ) { - OutlinedButton( - onClick = { cartViewModel.clearCart() }, - modifier = Modifier + item { + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.TopEnd ) { - Text("Empty Cart", color = Color.Red) + OutlinedButton( + onClick = { cartViewModel.clearCart() }, + modifier = Modifier + ) { + Text("Empty Cart", color = Color.Red) + } } } - LazyColumn(modifier = Modifier.weight(1f)) { - items(cartItems.size) { index -> - ProductCard(product = cartItems[index].product, onClick = {}) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "Quantity: ${cartItems[index].quantity}", - modifier = Modifier.padding(start = 16.dp) - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "Total: \$${String.format(Locale.US, "%.2f", cartItems[index].totalPrice())}", - modifier = Modifier.padding(start = 16.dp) - ) - } + items(cartItems.size) { index -> + ProductCard(product = cartItems[index].product, onProductClick = {}) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Quantity: ${cartItems[index].quantity}", + modifier = Modifier.padding(start = 16.dp) + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Total: \$${String.format(Locale.US, "%.2f", cartItems[index].totalPrice())}", + modifier = Modifier.padding(start = 16.dp) + ) + Spacer(modifier = Modifier.height(16.dp)) } - Spacer(modifier = Modifier.height(16.dp)) + item { + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "Total Price: \$${String.format(Locale.US, "%.2f", cartViewModel.getTotalPrice())}", + modifier = Modifier.padding(16.dp) + ) - Text( - text = "Total Price: \$${String.format(Locale.US, "%.2f", cartViewModel.getTotalPrice())}", - modifier = Modifier.padding(16.dp) - ) + Button( + onClick = onCheckoutClick, + enabled = !isCartEmpty, + colors = ButtonDefaults.buttonColors( + containerColor = if (isCartEmpty) Color.Gray else MaterialTheme.colorScheme.primary + ), + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Text("Checkout") + } - Button( - onClick = onCheckoutClick, - enabled = !isCartEmpty, - colors = ButtonDefaults.buttonColors( - containerColor = if (isCartEmpty) Color.Gray else MaterialTheme.colorScheme.primary - ), - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - ) { - Text("Checkout") + Spacer(modifier = Modifier.height(32.dp)) + RecommendedSection(recommendedProducts = recommendedProducts, onProductClick = onProductClick) } } } - diff --git a/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/cart/CheckoutInfo.kt b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/cart/CheckoutInfo.kt index 0b5a3a92d..c725ff34d 100644 --- a/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/cart/CheckoutInfo.kt +++ b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/cart/CheckoutInfo.kt @@ -15,6 +15,9 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp +import io.opentelemetry.android.demo.ui.shop.components.UpPressButton +import androidx.compose.ui.Alignment +import androidx.compose.ui.text.style.TextAlign data class ShippingInfo( var email: String = "", @@ -66,7 +69,10 @@ fun SectionHeader(title: String) { Text( text = title, style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(vertical = 8.dp) + textAlign = TextAlign.Center, + modifier = Modifier + .padding(vertical = 8.dp) + .fillMaxWidth() ) } @@ -92,56 +98,70 @@ fun InfoFieldsSection( } @Composable -fun InfoScreen() { +fun InfoScreen( + upPress: () -> Unit +) { var shippingInfo by remember { mutableStateOf(ShippingInfo()) } var paymentInfo by remember { mutableStateOf(PaymentInfo()) } val focusManager = LocalFocusManager.current - val canProceed = shippingInfo.isComplete() && paymentInfo.isComplete() - Column( + Box( modifier = Modifier .fillMaxSize() - .padding(16.dp) .background(Color.White) - .clickable { focusManager.clearFocus() } - .verticalScroll(rememberScrollState()) ) { - SectionHeader(title = "Shipping Address") - - InfoFieldsSection( - fields = listOf( - Triple("E-mail Address", shippingInfo.email) { shippingInfo = shippingInfo.copy(email = it) }, - Triple("Street Address", shippingInfo.streetAddress) { shippingInfo = shippingInfo.copy(streetAddress = it) }, - Triple("Zip Code", shippingInfo.zipCode) { shippingInfo = shippingInfo.copy(zipCode = it) }, - Triple("City", shippingInfo.city) { shippingInfo = shippingInfo.copy(city = it) }, - Triple("State", shippingInfo.state) { shippingInfo = shippingInfo.copy(state = it) }, - Triple("Country", shippingInfo.country) { shippingInfo = shippingInfo.copy(country = it) } + // Content inside a Column + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + .clickable { focusManager.clearFocus() } + .verticalScroll(rememberScrollState()) + ) { + SectionHeader(title = "Shipping Address") + + InfoFieldsSection( + fields = listOf( + Triple("E-mail Address", shippingInfo.email) { shippingInfo = shippingInfo.copy(email = it) }, + Triple("Street Address", shippingInfo.streetAddress) { shippingInfo = shippingInfo.copy(streetAddress = it) }, + Triple("Zip Code", shippingInfo.zipCode) { shippingInfo = shippingInfo.copy(zipCode = it) }, + Triple("City", shippingInfo.city) { shippingInfo = shippingInfo.copy(city = it) }, + Triple("State", shippingInfo.state) { shippingInfo = shippingInfo.copy(state = it) }, + Triple("Country", shippingInfo.country) { shippingInfo = shippingInfo.copy(country = it) } + ) ) - ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(16.dp)) - SectionHeader(title = "Payment Method") + SectionHeader(title = "Payment Method") - InfoFieldsSection( - fields = listOf( - Triple("Credit Card Number", paymentInfo.creditCardNumber) { paymentInfo = paymentInfo.copy(creditCardNumber = it) }, - Triple("Month", paymentInfo.expiryMonth) { paymentInfo = paymentInfo.copy(expiryMonth = it) }, - Triple("Year", paymentInfo.expiryYear) { paymentInfo = paymentInfo.copy(expiryYear = it) }, - Triple("CVV", paymentInfo.cvv) { paymentInfo = paymentInfo.copy(cvv = it) } + InfoFieldsSection( + fields = listOf( + Triple("Credit Card Number", paymentInfo.creditCardNumber) { paymentInfo = paymentInfo.copy(creditCardNumber = it) }, + Triple("Month", paymentInfo.expiryMonth) { paymentInfo = paymentInfo.copy(expiryMonth = it) }, + Triple("Year", paymentInfo.expiryYear) { paymentInfo = paymentInfo.copy(expiryYear = it) }, + Triple("CVV", paymentInfo.cvv) { paymentInfo = paymentInfo.copy(cvv = it) } + ) ) - ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(16.dp)) - Button( - onClick = { /*TODO Handle*/ }, - modifier = Modifier.fillMaxWidth(), - enabled = canProceed - ) { - Text("Proceed") + Button( + onClick = { /*TODO Handle*/ }, + modifier = Modifier.fillMaxWidth(), + enabled = canProceed + ) { + Text("Proceed") + } } + + UpPressButton( + upPress = upPress, + modifier = Modifier + .align(Alignment.TopStart) + .padding(8.dp) + ) } -} \ No newline at end of file +} diff --git a/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/components/UpPressButton.kt b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/components/UpPressButton.kt new file mode 100644 index 000000000..37b0af39e --- /dev/null +++ b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/components/UpPressButton.kt @@ -0,0 +1,33 @@ +package io.opentelemetry.android.demo.ui.shop.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex + +@Composable +fun UpPressButton( + upPress: () -> Unit, + modifier: Modifier = Modifier +) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Navigate Up", + tint = Color.Black, + modifier = modifier + .size(48.dp) + .background(Color.White, shape = CircleShape) + .padding(8.dp) + .zIndex(1f) + .clickable(onClick = upPress) + ) +} diff --git a/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/ProductCard.kt b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/ProductCard.kt index e5100efa9..03ed27620 100644 --- a/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/ProductCard.kt +++ b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/ProductCard.kt @@ -27,7 +27,12 @@ import io.opentelemetry.android.demo.gothamFont import io.opentelemetry.android.demo.model.Product @Composable -fun ProductCard(product: Product, onClick: (String) -> Unit) { +fun ProductCard( + product: Product, + onProductClick: (String) -> Unit, + modifier: Modifier = Modifier, + isNarrow: Boolean = false +) { val imageLoader = ImageLoader(LocalContext.current) val sourceProductImage = imageLoader.load(product.picture) Bitmap.createScaledBitmap(sourceProductImage, 120, 120, false) @@ -39,12 +44,12 @@ fun ProductCard(product: Product, onClick: (String) -> Unit) { Card( elevation = CardDefaults.cardElevation(defaultElevation = 10.dp), colors = cardColors, - modifier = Modifier + modifier = modifier .fillMaxSize() .height(200.dp) .wrapContentHeight() .padding(20.dp), - onClick = { onClick(product.id) } + onClick = { onProductClick(product.id) } ) { Row(modifier = Modifier .fillMaxSize() @@ -58,15 +63,16 @@ fun ProductCard(product: Product, onClick: (String) -> Unit) { } Column(modifier = Modifier.fillMaxWidth()) { Text( - product.name + "\n\n$" + product.priceValue(), + text = "${product.name}\n\n$${product.priceValue()}", fontFamily = gothamFont, style = TextStyle.Default.copy(textAlign = TextAlign.Right), - fontSize = 18.sp, + fontSize = if (isNarrow) 13.sp else 18.sp, modifier = Modifier - .padding(start = 15.dp, top = 25.dp) + .padding(start = 15.dp, top = 15.dp) .fillMaxWidth() ) } + } } } diff --git a/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/ProductDetails.kt b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/ProductDetails.kt index a5a317615..65a5f75b2 100644 --- a/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/ProductDetails.kt +++ b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/ProductDetails.kt @@ -19,62 +19,88 @@ import io.opentelemetry.android.demo.model.Product import io.opentelemetry.android.demo.ui.shop.components.QuantityChooser import androidx.lifecycle.viewmodel.compose.viewModel import io.opentelemetry.android.demo.ui.shop.cart.CartViewModel +import io.opentelemetry.android.demo.ui.shop.components.UpPressButton +import androidx.compose.ui.Alignment +import io.opentelemetry.android.demo.clients.ProductCatalogClient +import io.opentelemetry.android.demo.clients.RecommendationService @Composable -fun ProductDetails(product:Product, cartViewModel: CartViewModel = viewModel()){ - val imageLoader = ImageLoader(LocalContext.current) +fun ProductDetails( + product: Product, + cartViewModel: CartViewModel = viewModel(), + onProductClick: (String) -> Unit, + upPress: () -> Unit +) { + val context = LocalContext.current + val imageLoader = ImageLoader(context) val sourceProductImage = imageLoader.load(product.picture) var quantity by remember { mutableIntStateOf(1) } - Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp) - .verticalScroll(rememberScrollState()) + val productsClient = ProductCatalogClient(context) + val recommendationService = remember { RecommendationService(productsClient, cartViewModel) } + val recommendedProducts = remember { recommendationService.getRecommendedProducts(product) } + Box( + modifier = Modifier.fillMaxSize() ) { - Image( - bitmap = sourceProductImage.asImageBitmap(), - contentDescription = product.name, + Column( modifier = Modifier - .fillMaxWidth() - .height(300.dp) - ) - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = product.name, - fontFamily = gothamFont, - fontSize = 24.sp, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center - ) - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = product.description, - color = Color.Gray, - textAlign = TextAlign.Justify, - fontFamily = gothamFont, - fontSize = 16.sp, - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = "$${product.priceValue()}", - fontFamily = gothamFont, - fontSize = 24.sp, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center - ) - Spacer(modifier = Modifier.height(32.dp)) - QuantityChooser(quantity = quantity, onQuantityChange = { quantity = it }) - Spacer(modifier = Modifier.height(16.dp)) - Button( - onClick = { cartViewModel.addProduct(product, quantity) }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) + .fillMaxSize() + .padding(16.dp) + .verticalScroll(rememberScrollState()) ) { - Text(text = "Add to Cart") + Image( + bitmap = sourceProductImage.asImageBitmap(), + contentDescription = product.name, + modifier = Modifier + .fillMaxWidth() + .height(300.dp) + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = product.name, + fontFamily = gothamFont, + fontSize = 24.sp, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = product.description, + color = Color.Gray, + textAlign = TextAlign.Justify, + fontFamily = gothamFont, + fontSize = 16.sp, + modifier = Modifier.fillMaxWidth() + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = "$${product.priceValue()}", + fontFamily = gothamFont, + fontSize = 24.sp, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(32.dp)) + QuantityChooser(quantity = quantity, onQuantityChange = { quantity = it }) + Spacer(modifier = Modifier.height(16.dp)) + Button( + onClick = { cartViewModel.addProduct(product, quantity) }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) { + Text(text = "Add to Cart") + } + Spacer(modifier = Modifier.height(32.dp)) + RecommendedSection(recommendedProducts = recommendedProducts, onProductClick = onProductClick) } + + UpPressButton( + upPress = upPress, + modifier = Modifier + .align(Alignment.TopStart) + .padding(8.dp) + ) } } diff --git a/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/ProductList.kt b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/ProductList.kt index 8c26bd1f9..0fbec4f58 100644 --- a/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/ProductList.kt +++ b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/ProductList.kt @@ -15,7 +15,7 @@ fun ProductList(products: List, onProductClick: (String) -> Unit) { LazyColumn(modifier = Modifier.fillMaxSize()) { items(products.size) { index -> Row { - ProductCard(products[index], onClick = onProductClick) + ProductCard(products[index], onProductClick = onProductClick) } } item { diff --git a/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/RecommendedSection.kt b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/RecommendedSection.kt new file mode 100644 index 000000000..5d26b93a0 --- /dev/null +++ b/demo-app/src/main/java/io/opentelemetry/android/demo/ui/shop/products/RecommendedSection.kt @@ -0,0 +1,55 @@ +package io.opentelemetry.android.demo.ui.shop.products + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import io.opentelemetry.android.demo.gothamFont +import io.opentelemetry.android.demo.model.Product + +@Composable +fun RecommendedSection( + recommendedProducts: List, + onProductClick: (String) -> Unit +) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(0.dp) + ) { + Text( + text = "You may also like", + fontFamily = gothamFont, + fontSize = 20.sp, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp), + textAlign = TextAlign.Start + ) + + LazyRow( + modifier = Modifier.fillMaxWidth(), + contentPadding = PaddingValues(horizontal = 0.dp), + horizontalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(8.dp) + ) { + items(recommendedProducts) { product -> + ProductCard( + product = product, + onProductClick = onProductClick, + modifier = Modifier.width(300.dp).height(170.dp), + isNarrow = true + ) + } + } + } +}