Skip to content

Instantly share code, notes, and snippets.

@Mikkareem
Created December 9, 2023 13:27
Show Gist options
  • Save Mikkareem/62f431f34174c1e20cc5e6addeb54586 to your computer and use it in GitHub Desktop.
Save Mikkareem/62f431f34174c1e20cc5e6addeb54586 to your computer and use it in GitHub Desktop.

Food App UI Design using Jetpack Compose.

  1. Custom Layout (Measurement and Placement)
  2. Custom Strikethrough text (Drawing)

3 Screens.

  1. Food Overview Screen
  2. Food Deals Screen
  3. Food Order Details Screen

Note: This design is a clone of UI defined in Dribbble.com The Copyright is subjected to the appropriate owner itself.

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.twotone.Menu
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.techullurgy.composeuisapplication.R
@Preview
@Composable
fun FoodSpecialDealsScreen() {
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xfff6f7f7))
.padding(16.dp)
) {
TopBar()
Spacer(modifier = Modifier.height(100.dp))
FoodSpecialDealsMainSection()
}
BottomSection(
Modifier
.fillMaxWidth()
.align(Alignment.BottomStart))
}
}
@Composable
private fun TopBar() {
Row {
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) {
Text(text = "Special Deals")
}
Icon(imageVector = Icons.TwoTone.Menu, contentDescription = null)
}
}
@Composable
private fun FoodSpecialDealsMainSection() {
Column {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Sake sushi", fontSize = 32.sp, fontWeight = FontWeight.ExtraBold)
Spacer(modifier = Modifier.height(16.dp))
Text(text = "$4.99", fontSize = 24.sp)
Spacer(modifier = Modifier.height(16.dp))
Text(text = "4.9 (109 Reviews)", fontSize = 16.sp, color = Color.Gray)
}
Spacer(modifier = Modifier.height(16.dp))
Image(painter = painterResource(id = R.drawable.food2), contentDescription = null)
}
}
@Composable
private fun BottomSection(
modifier: Modifier = Modifier
) {
val cartItems = listOf(
CartItem(itemName = "Sake Sushi", itemCount = 2, originalPrice = 9.89, discountedPrice = 4.99),
CartItem(itemName = "Ebi Sushi", itemCount = 1, originalPrice = 2.99, discountedPrice = 1.45)
)
Column(
modifier = modifier
.clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))
.background(Color(0xff3f434b))
.padding(16.dp)
) {
ProvideTextStyle(value = LocalTextStyle.current.copy(color = Color.White)) {
Text(text = "Order List", fontSize = 24.sp)
cartItems.forEach {
CartItemView(item = it)
}
Button(
onClick = { /*TODO*/ },
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xfff46859)
),
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Pay $6.44")
}
}
}
}
@Composable
private fun CartItemView(
item: CartItem
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(70.dp)
.clip(CircleShape)
) {
Image(
painter = painterResource(id = R.drawable.food2),
contentDescription = null,
contentScale = ContentScale.Crop
)
}
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(
text = buildAnnotatedString {
append(item.itemName)
append(" ")
withStyle(SpanStyle(fontSize = 12.sp, color = LocalTextStyle.current.color.copy(alpha = 0.6f))) {
append("x${item.itemCount}")
}
}
)
Row {
StrikeText(text = "$${item.originalPrice}")
Spacer(modifier = Modifier.width(8.dp))
Text(text = "$${item.discountedPrice}")
}
}
Spacer(modifier = Modifier.weight(1f))
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = buildAnnotatedString {
withStyle(SpanStyle(fontSize = 24.sp, baselineShift = BaselineShift(0.2f))) {
append("--")
}
},
textAlign = TextAlign.Center,
modifier = Modifier.drawBehind {
drawCircle(color = Color.White, radius = size.width, style = Stroke(width = 1.dp.toPx()))
}
)
Spacer(modifier = Modifier.width(32.dp))
Text(text = "${item.itemCount}", fontSize = 24.sp)
Spacer(modifier = Modifier.width(32.dp))
Text(
text = "+",
fontSize = 24.sp,
textAlign = TextAlign.Center,
modifier = Modifier.drawBehind {
drawCircle(color = Color.White, radius = size.width, style = Stroke(width = 1.dp.toPx()))
}
)
}
}
}
@Composable
private fun StrikeText(text: String) {
val strikeColor = LocalTextStyle.current.color
Text(
text = text,
modifier = Modifier
.drawWithContent {
drawContent()
drawLine(
color = strikeColor,
start = Offset(x = size.width, y = 2.dp.toPx()),
end = Offset(x = 0f, y = size.height - 2.dp.toPx()),
strokeWidth = 2.dp.toPx()
)
}
)
}
private data class CartItem(
val itemName: String,
val itemCount: Int,
val originalPrice: Double,
val discountedPrice: Double
)
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Email
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.Phone
import androidx.compose.material.icons.twotone.Menu
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.techullurgy.composeuisapplication.R
@Preview
@Composable
fun FoodOrderDetailsScreen() {
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xfff6f7f7))
.padding(16.dp)
) {
TopBar()
Spacer(modifier = Modifier.height(80.dp))
FoodSpecialDealsMainSection()
}
BottomSection(
Modifier
.fillMaxWidth()
.align(Alignment.BottomStart)
)
}
}
@Composable
private fun TopBar() {
Row {
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) {
Text(text = "Special Deals")
}
Icon(imageVector = Icons.TwoTone.Menu, contentDescription = null)
}
}
@Composable
private fun FoodSpecialDealsMainSection() {
Column {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Sake sushi", fontSize = 32.sp, fontWeight = FontWeight.ExtraBold)
Spacer(modifier = Modifier.height(16.dp))
Text(text = "$4.99", fontSize = 24.sp)
Spacer(modifier = Modifier.height(16.dp))
Text(text = "4.9 (109 Reviews)", fontSize = 16.sp, color = Color.Gray)
}
Spacer(modifier = Modifier.height(16.dp))
Image(painter = painterResource(id = R.drawable.food2), contentDescription = null)
}
}
@Composable
private fun BottomSection(
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))
.background(Color(0xff3f434b))
.padding(16.dp)
) {
ProvideTextStyle(value = LocalTextStyle.current.copy(color = Color.White)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.size(70.dp)
.clip(CircleShape)
) {
Image(
painter = painterResource(id = R.drawable.sample_profile2),
contentDescription = null,
contentScale = ContentScale.Crop
)
}
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(text = "Trojan Fox", fontSize = 24.sp)
Text(text = "Your Courier")
}
Spacer(modifier = Modifier.weight(1f))
Box(
modifier = Modifier
.clip(CircleShape)
.background(Color(0xfff46859))
.padding(8.dp)
) {
Icon(imageVector = Icons.Default.Email, contentDescription = null, tint = Color.White, modifier = Modifier.size(20.dp))
}
Spacer(modifier = Modifier.width(16.dp))
Box(
modifier = Modifier
.clip(CircleShape)
.background(Color(0xfff46859))
.padding(8.dp)
) {
Icon(imageVector = Icons.Default.Phone, contentDescription = null, tint = Color.White, modifier = Modifier.size(20.dp))
}
}
Spacer(modifier = Modifier.height(16.dp))
Column(
modifier = Modifier
.clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))
.background(Color.White)
.padding(16.dp)
) {
ProvideTextStyle(LocalTextStyle.current.copy(color = Color.Black)) {
Row {
Box(
modifier = Modifier
.clip(CircleShape)
.background(Color(0xfff46859).copy(alpha = 0.2f))
.padding(8.dp)
) {
Icon(painter = painterResource(id = R.drawable.baseline_access_time_24), contentDescription = null, tint = Color(0xfff46859))
}
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(text = "Your Delivery time", color = LocalTextStyle.current.color.copy(alpha = 0.5f))
Text(text = "12 Minutes", fontSize = 20.sp)
}
}
Spacer(modifier = Modifier.height(16.dp))
Row {
Box(
modifier = Modifier
.clip(CircleShape)
.background(Color(0xfff46859).copy(alpha = 0.2f))
.padding(8.dp)
) {
Icon(imageVector = Icons.Default.LocationOn, contentDescription = null, tint = Color(0xfff46859))
}
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(text = "Your Delivery Address", color = LocalTextStyle.current.color.copy(alpha = 0.5f))
Text(text = "4517 Washington Ave. Manchester, Kentucky 39495", fontSize = 20.sp)
}
}
Spacer(modifier = Modifier.height(16.dp))
}
Button(
onClick = { /*TODO*/ },
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xfff46859)
),
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Order Details", fontSize = 20.sp)
}
}
}
}
}
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
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
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.ShoppingCart
import androidx.compose.material.icons.outlined.List
import androidx.compose.material.icons.outlined.Notifications
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.techullurgy.composeuisapplication.R
@Preview
@Composable
fun FoodOverviewScreen() {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.fillMaxSize()
.background(Color(0xfff6f7f7))
.padding(16.dp)
) {
TopBarView()
Spacer(modifier = Modifier.height(16.dp))
SearchBarView()
Spacer(modifier = Modifier.height(16.dp))
SpecialDealsCard()
Spacer(modifier = Modifier.height(16.dp))
PopularFoodsSection()
Spacer(modifier = Modifier.height(16.dp))
HistoryFoodsSection()
}
}
@Composable
private fun TopBarView() {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(70.dp)
.clip(CircleShape)
.background(Color.White)
) {
Image(
painter = painterResource(id = R.drawable.sample_profile2),
contentDescription = null,
contentScale = ContentScale.Crop
)
}
Spacer(modifier = Modifier.width(10.dp))
Column(
modifier = Modifier.weight(1f)
) {
Text(text = "Hi, Nanas")
Text(text = "What do you want to eat?", fontSize = 18.sp)
}
Icon(imageVector = Icons.Outlined.Notifications, contentDescription = null, modifier = Modifier.size(36.dp))
}
}
@Composable
private fun SearchBarView() {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
TextField(
value = "",
onValueChange = {},
shape = RoundedCornerShape(50),
leadingIcon = {
Icon(imageVector = Icons.Default.Search, contentDescription = null)
},
placeholder = {
Text(text = "Search for food")
},
colors = TextFieldDefaults.colors(
unfocusedContainerColor = Color.White,
focusedContainerColor = Color.White,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
),
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(10.dp))
Box(
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(Color.Black),
contentAlignment = Alignment.Center
) {
Icon(imageVector = Icons.Outlined.List, contentDescription = null, modifier = Modifier.size(28.dp), tint = Color.White)
}
}
}
@Composable
private fun SpecialDealsCard() {
Layout(
modifier = Modifier
.clip(RoundedCornerShape(20.dp))
.background(Color(0xff3f434b)),
content = {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
ProvideTextStyle(value = LocalTextStyle.current.copy(color = Color.White)) {
Text(text = "Special Deals")
Text(text = "50% OFF", fontSize = 34.sp, fontWeight = FontWeight.ExtraBold)
Text(text = "and get free delivery", fontSize = 14.sp)
}
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = { /*TODO*/ },
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xfff46859)
)
) {
Text(text = "Order now")
}
}
Box(
Modifier
.aspectRatio(1f)
// .clip(CircleShape)
// .background(Color.Yellow)
) {
Image(
painter = painterResource(id = R.drawable.food2),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
}
}
) { measurables, constraints ->
val leftConstraints = constraints.copy(maxWidth = constraints.maxWidth / 2)
val leftPlaceable = measurables[0].measure(leftConstraints)
val maxHeight = leftPlaceable.height
val rightConstraints = leftConstraints.copy(maxHeight = maxHeight)
val rightPlaceable = measurables[1].measure(rightConstraints)
layout(constraints.maxWidth, maxHeight) {
leftPlaceable.place(x = 0, y = 0)
rightPlaceable.place(x = leftConstraints.maxWidth, y = 0)
}
}
}
@Composable
private fun PopularFoodsSection() {
val popularFoods = listOf(
FoodCardDetail(title = "Ebi Sushi", rating = 4.9, totalReviews = 109, price = 4.99),
FoodCardDetail(title = "Sake Sushi", rating = 3.6, totalReviews = 87, price = 2.88),
FoodCardDetail(title = "Rato Kachap", rating = 3.55, totalReviews = 69, price = 8.14),
FoodCardDetail(title = "Guli Paind", rating = 2.27, totalReviews = 179, price = 6.73),
)
Column {
OverviewSection(title = "Popular Foods")
Spacer(modifier = Modifier.height(16.dp))
FoodCardRow(foodCardDetails = popularFoods)
}
}
@Composable
private fun HistoryFoodsSection() {
val history = listOf(
FoodCardDetail(title = "Gar Repto", rating = 4.9, totalReviews = 109, price = 4.99),
FoodCardDetail(title = "Ruji Tawan", rating = 3.6, totalReviews = 87, price = 2.88),
FoodCardDetail(title = "Kesp Rota", rating = 3.55, totalReviews = 69, price = 8.14),
FoodCardDetail(title = "loet poty", rating = 2.27, totalReviews = 179, price = 6.73),
)
Column {
OverviewSection(title = "Based on your history")
Spacer(modifier = Modifier.height(16.dp))
FoodCardRow(foodCardDetails = history)
}
}
@Composable
private fun FoodCard(
foodCardDetail: FoodCardDetail
) {
Column(
modifier = Modifier
.width(IntrinsicSize.Max)
.clip(RoundedCornerShape(20.dp))
.background(Color.White)
.padding(16.dp)
) {
Box(modifier = Modifier
.fillMaxWidth()
.height(100.dp)
// .background(Color(0xfff46859))
) {
Image(
painter = painterResource(id = R.drawable.food2),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.clipToBounds()
.fillMaxWidth()
.aspectRatio(1f)
)
}
Row(
verticalAlignment = Alignment.Bottom
) {
Column {
Text(text = foodCardDetail.title, fontSize = 20.sp)
Spacer(modifier = Modifier.height(8.dp))
Text(text = "${foodCardDetail.rating}(${foodCardDetail.totalReviews} reviews)", fontSize = 12.sp, color = Color.Gray)
Spacer(modifier = Modifier.height(8.dp))
Text(text = "$${foodCardDetail.price}", fontSize = 15.sp)
}
Spacer(modifier = Modifier.width(8.dp))
Box(
modifier = Modifier
.clip(CircleShape)
.background(Color(0xfff46859))
.padding(8.dp)
) {
Icon(imageVector = Icons.Default.ShoppingCart, contentDescription = null, tint = Color.White, modifier = Modifier.size(20.dp))
}
}
}
}
@Composable
private fun OverviewSection(
title: String
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(text = title, fontSize = 28.sp, modifier = Modifier.weight(1f))
Text(text = "See all")
}
}
@Composable
private fun FoodCardRow(
foodCardDetails: List<FoodCardDetail>
) {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(8.dp)
) {
items(foodCardDetails) {
FoodCard(it)
}
}
}
private data class FoodCardDetail(
val title: String,
val rating: Double,
val totalReviews: Int,
val price: Double
)
@Mikkareem
Copy link
Author

Mikkareem commented Dec 9, 2023

The Demo of this Design is available in this Linkedin Post

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment