Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Demo app: Recommendations #554

Merged
merged 15 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<Product> {
return getAllNonCartProducts().filter { it.id != currentProduct.id }
.shuffled().take(numberOfProducts)
}

fun getRecommendedProducts(numberOfProducts: Int = 4): List<Product> {
return getAllNonCartProducts().shuffled().take(numberOfProducts)
}

private fun getAllNonCartProducts(): List<Product>{
val allProducts = productCatalogClient.get()
val cartItems = cartViewModel.cartItems.value

return allProducts.filter { product -> cartItems.none { it.product.id == product.id } }
}
}
breedx-splk marked this conversation as resolved.
Show resolved Hide resolved

Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down Expand Up @@ -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()})
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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 = "",
Expand Down Expand Up @@ -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()
)
}

Expand All @@ -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)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -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)
)
}
Loading