/**
* Requires the following dependencies. Add them to app/build.gradle.kts
*
* dependencies {
* implementation("com.composables:compose-uri-painter:1.0.2")
* }
*
*/
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Image
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
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.draw.rotate
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.composables.uripainter.rememberUriPainter
@Composable
fun CardExpandable() {
data class Review(val stars: Float, val reviewer: String, val timestamp: String, val summary: String)
val reviews = listOf(
Review(
stars = 5f,
reviewer = "John",
timestamp = "2 hours ago",
summary = "Best coffee in town. I love the atmosphere and the staff is super friendly."
),
Review(
stars = 4.5f,
reviewer = "Cassidy",
timestamp = "1 day ago",
summary = "Great place to unwind. The cappuccino was excellent, and the pastries were delicious."
),
Review(
stars = 3.5f,
reviewer = "James",
timestamp = "3 days ago",
summary = "Decent coffee, but a bit crowded during peak hours. Could use more seating space."
),
Review(
stars = 4f,
reviewer = "Cassidy",
timestamp = "5 days ago",
summary = "Nice little coffee spot. I enjoyed the latte and the artsy decor."
),
Review(
stars = 5f,
reviewer = "Rony",
timestamp = "1 week ago",
summary = "Charming café with a Parisian vibe. The espresso here is top-notch."
),
Review(
stars = 3.5f,
reviewer = "Fraklin",
timestamp = "2 weeks ago",
summary = "Average coffee, but the location is convenient for a quick stop."
)
)
@Composable
fun RatingBar(
rating: Float,
maxRating: Int,
modifier: Modifier = Modifier,
fillColor: Color = Color(0xFFFFC107),
emptyColor: Color = Color(0xFFEEEEEE)
) {
val FirstHalf = object : Shape {
override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline {
return Outline.Rectangle(Rect(0f, 0f, size.width / 2, size.height))
}
}
// offset matches the padding of the Star Icon
Row(modifier.offset(x = (-2).dp)) {
repeat(maxRating) { i ->
Box(Modifier.size(24.dp)) {
val lastFullIndex = (rating - 1).toInt()
Icon(
imageVector = Icons.Rounded.Star,
contentDescription = null,
tint = emptyColor,
modifier = Modifier.matchParentSize()
)
when {
i <= lastFullIndex -> {
Icon(
imageVector = Icons.Rounded.Star,
contentDescription = null,
tint = fillColor,
modifier = Modifier.matchParentSize()
)
}
i == lastFullIndex + 1 -> {
Icon(
imageVector = Icons.Rounded.Star,
contentDescription = null,
tint = fillColor,
modifier = Modifier.matchParentSize().clip(FirstHalf)
)
}
}
}
}
}
}
OutlinedCard {
var expanded by remember { mutableStateOf(false) }
val degrees by animateFloatAsState(if (expanded) -90f else 90f)
Column(Modifier.width(380.dp)) {
Image(
rememberUriPainter("https://images.unsplash.com/photo-1491147334573-44cbb4602074?w=320"),
modifier = Modifier.clip(CardDefaults.outlinedShape).aspectRatio(16 / 9f),
contentScale = ContentScale.Crop,
contentDescription = null
)
Column(Modifier.padding(start = 16.dp, end = 16.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Cafe de Paris", style = MaterialTheme.typography.titleLarge)
Spacer(Modifier.weight(1f))
IconButton(onClick = { expanded = expanded.not() }) {
Icon(
imageVector = Icons.Filled.ChevronRight,
contentDescription = if (expanded) "Hide details" else "Show more details",
modifier = Modifier.rotate(degrees)
)
}
}
Row(verticalAlignment = Alignment.CenterVertically) {
RatingBar(rating = 4.5f, maxRating = 5)
Spacer(Modifier.width(8.dp))
Text("4.5", style = MaterialTheme.typography.bodyMedium)
Spacer(Modifier.width(4.dp))
Text("(${1280})", style = MaterialTheme.typography.bodyMedium)
}
Spacer(Modifier.height(16.dp))
Box {
[email protected](visible = expanded) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.height(190.dp),
contentPadding = PaddingValues(vertical = 8.dp)
) {
item { Text("Reviews (${reviews.size})") }
reviews.forEach { review ->
val rating = review.stars
val reviewer = review.reviewer
val timestamp = review.timestamp
val summary = review.summary
item {
Column {
Text(text = reviewer, style = MaterialTheme.typography.bodyLarge)
Spacer(Modifier.height(8.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
RatingBar(rating = rating, maxRating = 5)
Text(text = timestamp, style = MaterialTheme.typography.bodyMedium)
}
Spacer(Modifier.height(16.dp))
Text(text = summary, style = MaterialTheme.typography.bodyMedium)
}
}
}
}
}
[email protected](visible = expanded) {
HorizontalDivider()
}
}
}
}
}
}