From adb50f913596da0682db4aa672df0e30addf1c02 Mon Sep 17 00:00:00 2001 From: KaushalVasava Date: Thu, 4 Apr 2024 11:08:42 +0530 Subject: [PATCH 1/4] add order ui --- .../composeui/screen/AuthenticationScreen.kt | 394 ++++++++++++++++++ .../app/composeui/screen/OrderScreen.kt | 173 ++++++++ .../app/composeui/ui/navigation/AppNavHost.kt | 6 +- .../composeui/ui/navigation/NavigationItem.kt | 1 + .../app/composeui/util/ValidUtil.kt | 34 ++ app/src/main/res/drawable/ic_facebook.xml | 5 + app/src/main/res/drawable/ic_google.xml | 11 + app/src/main/res/drawable/ic_location.xml | 5 + app/src/main/res/drawable/ic_visibility.xml | 5 + .../main/res/drawable/ic_visibility_off.xml | 5 + 10 files changed, 638 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/kaushalvasava/app/composeui/screen/AuthenticationScreen.kt create mode 100644 app/src/main/java/com/kaushalvasava/app/composeui/screen/OrderScreen.kt create mode 100644 app/src/main/java/com/kaushalvasava/app/composeui/util/ValidUtil.kt create mode 100644 app/src/main/res/drawable/ic_facebook.xml create mode 100644 app/src/main/res/drawable/ic_google.xml create mode 100644 app/src/main/res/drawable/ic_location.xml create mode 100644 app/src/main/res/drawable/ic_visibility.xml create mode 100644 app/src/main/res/drawable/ic_visibility_off.xml diff --git a/app/src/main/java/com/kaushalvasava/app/composeui/screen/AuthenticationScreen.kt b/app/src/main/java/com/kaushalvasava/app/composeui/screen/AuthenticationScreen.kt new file mode 100644 index 0000000..eb38a20 --- /dev/null +++ b/app/src/main/java/com/kaushalvasava/app/composeui/screen/AuthenticationScreen.kt @@ -0,0 +1,394 @@ +package com.kaushalvasava.app.composeui.screen + +import android.widget.Toast +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +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.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +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.drawBehind +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +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.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import com.kaushalvasava.app.composeui.R +import com.kaushalvasava.app.composeui.ui.navigation.NavigationItem +import com.kaushalvasava.app.composeui.util.ValidUtil.isValidEmail +import com.kaushalvasava.app.composeui.util.ValidUtil.isValidName +import com.kaushalvasava.app.composeui.util.ValidUtil.isValidPasswordFormat + +@Preview(showBackground = false) +@Composable +fun AuthenticationScreen(navController: NavController = rememberNavController()) { + + var isNewUser by rememberSaveable { + mutableStateOf(true) + } + val backgroundColor = MaterialTheme.colorScheme.background + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .fillMaxSize() + .drawBehind { + drawRect( + Brush.linearGradient( + colors = listOf( + Color.Blue, + backgroundColor, + Color.Blue, + ) + ) + ) + } + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 24.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + if (isNewUser) { + "Don't have an account?" + } else { + "Already have an account?" + }, + style = MaterialTheme.typography.bodyMedium, + color = Color.White + ) + Spacer(modifier = Modifier.width(8.dp)) + Button( + onClick = { + isNewUser = !isNewUser + }, colors = ButtonDefaults.buttonColors( + containerColor = backgroundColor.copy(0.3f), + contentColor = Color.White + ), + shape = RoundedCornerShape(8.dp) + ) { + AnimatedContent(targetState = isNewUser, label = "") { + Text( + if (it) { + "Sign up" + } else { + "Sign in" + } + ) + } + } + } + Image( + painter = painterResource(id = R.drawable.s_2), + contentScale = ContentScale.Crop, + contentDescription = "logo", + modifier = Modifier + .size(200.dp, 150.dp) + .rotate(-20f) + .clip(RoundedCornerShape(16.dp)) + .align(Alignment.CenterHorizontally) + + ) + Text( + "Unique", + style = MaterialTheme.typography.displayLarge, + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + Spacer(modifier = Modifier.height(16.dp)) + HorizontalDivider( + modifier = Modifier + .padding(horizontal = 32.dp) + .clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)), + thickness = 12.dp, color = MaterialTheme.colorScheme.background.copy(0.5f) + ) + InfoCard(modifier = Modifier, isNewUser = { isNewUser }) { + navController.navigate(NavigationItem.PRODUCTS) + } + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun InfoCard( + modifier: Modifier = Modifier, + isNewUser: () -> Boolean, + onSubmitClick: () -> Unit, +) { + val context = LocalContext.current + var email by rememberSaveable { + mutableStateOf("") + } + var name by rememberSaveable { + mutableStateOf("") + } + var isPwdVisible by rememberSaveable { + mutableStateOf(false) + } + val maxChar = 10 + var password by rememberSaveable { + mutableStateOf("") + } + + val isValid by remember { + derivedStateOf { + isValidEmail(email) && isValidPasswordFormat(password) + && (if (isNewUser()) isValidName(name) else true) + } + } + val msg = + "Password is Wrong!\n" + + "Please enter at least 1 digit, 1 upper case and lowercase letter, 1 special character, no white spaces, at least 8 character" + + Column( + modifier + .fillMaxWidth() + .clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)) + .background(MaterialTheme.colorScheme.background) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + AnimatedContent(targetState = isNewUser(), label = "") { + Text( + if (it) "Let's make life stylish" else "Welcome Back", + fontWeight = FontWeight.Bold, + fontSize = 32.sp + ) + } + Text( + "Enter your details below", + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Light + ) + AnimatedContent(targetState = isNewUser(), label = "flowRow") { + FlowRow( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + if (it) { + OutlinedTextField( + value = name, + onValueChange = { + name = it + }, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(12.dp), + textStyle = TextStyle(fontSize = 16.sp), + placeholder = { + Text("Enter name", color = Color.Gray) + }, + supportingText = { + if (!isValidName(name) && name.isNotEmpty()) { + Text( + modifier = Modifier.fillMaxWidth(), + text = "Name is wrong!", + color = MaterialTheme.colorScheme.error + ) + } + }, + isError = name.isNotEmpty() && !isValidName(name), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next) + ) + } + OutlinedTextField( + value = email, + onValueChange = { + email = it + }, + modifier = Modifier.weight(1f), + textStyle = TextStyle(fontSize = 16.sp), + placeholder = { + Text("Enter email", color = Color.Gray) + }, + shape = RoundedCornerShape(12.dp), + supportingText = { + if (!isValidEmail(email) && email.isNotEmpty()) { + Text( + modifier = Modifier.fillMaxWidth(), + text = "Email is wrong!", + color = MaterialTheme.colorScheme.error + ) + } + }, + isError = email.isNotEmpty() && !isValidEmail(email), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next) + ) + OutlinedTextField( + value = password, + onValueChange = { + if (it.length <= maxChar) password = it + }, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(12.dp), + visualTransformation = + if (isPwdVisible) + VisualTransformation.None + else + PasswordVisualTransformation(), + trailingIcon = { + IconButton( + onClick = { + isPwdVisible = !isPwdVisible + } + ) { + Icon( + if (isPwdVisible) + painterResource(id = R.drawable.ic_visibility_off) + else + painterResource(id = R.drawable.ic_visibility), + contentDescription = null + ) + } + }, + supportingText = { + if (!isValidPasswordFormat(password) && password.isNotEmpty()) { + Text( + modifier = Modifier.fillMaxWidth(), + text = msg, + color = MaterialTheme.colorScheme.error + ) + } + }, + textStyle = TextStyle(fontSize = 16.sp), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done), + placeholder = { + Text("Enter password", color = Color.Gray) + }, + isError = password.isNotEmpty() && !isValidPasswordFormat(password), + ) + } + } + Button( + onClick = { + if (isValid) { + onSubmitClick() + } else { + Toast.makeText(context, "Please enter all valid details", Toast.LENGTH_SHORT) + .show() + } + }, + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .drawBehind { + drawRect( + Brush.linearGradient( + colors = listOf( + Color.Blue, + Color.Magenta + ) + ) + ) + }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = Color.White + ), + shape = RoundedCornerShape(12.dp) + ) { + Text( + if (isNewUser()) "Sign up" else "Sign in", + modifier = Modifier.padding(vertical = 8.dp) + ) + } + AnimatedVisibility(!isNewUser()) { + TextButton(onClick = { + //forgot password + }) { + Text("Forgot your password?") + } + } + Row( + Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceAround, + verticalAlignment = Alignment.CenterVertically + ) { + HorizontalDivider(Modifier.weight(1f)) + AnimatedContent(targetState = isNewUser(), label = "dg") { + Text( + if (it) { + "Or sign up with" + } else { + "Or sign in with" + }, + modifier = Modifier.weight(1f), + textAlign = TextAlign.Center + ) + } + HorizontalDivider(Modifier.weight(1f)) + } + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + OutlinedButton(onClick = { /*TODO*/ }, shape = RoundedCornerShape(8.dp)) { + Icon( + painter = painterResource(id = R.drawable.ic_google), + contentDescription = "google", + modifier = Modifier.size(ButtonDefaults.IconSize), + tint = Color.Unspecified + ) + Spacer(modifier = Modifier.width(16.dp)) + Text(text = "Google") + } + OutlinedButton(onClick = { /*TODO*/ }, shape = RoundedCornerShape(8.dp)) { + Icon( + painter = painterResource(id = R.drawable.ic_facebook), + contentDescription = "Facebook", + modifier = Modifier.size(ButtonDefaults.IconSize), + tint = Color.Unspecified + ) + Spacer(modifier = Modifier.width(16.dp)) + Text(text = "Facebook") + } + } + Spacer(Modifier.weight(1f)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kaushalvasava/app/composeui/screen/OrderScreen.kt b/app/src/main/java/com/kaushalvasava/app/composeui/screen/OrderScreen.kt new file mode 100644 index 0000000..c9b38bc --- /dev/null +++ b/app/src/main/java/com/kaushalvasava/app/composeui/screen/OrderScreen.kt @@ -0,0 +1,173 @@ +package com.kaushalvasava.app.composeui.screen + +import androidx.annotation.DrawableRes +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.fillMaxHeight +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.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBack +import androidx.compose.material3.Card +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.VerticalDivider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +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.Color +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape +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.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.kaushalvasava.app.composeui.R +import kotlin.math.roundToInt + +data class Order( + val id: String, + // product id, and it's quantity + val items: Map, + val status: String, + val orderDate: Long, + val deliveryTime: Long, +) + +data class Product( + val id: String, + val name: String, + val price: String, + val minQuantity: Int, + val maxQuantity: Int, + @DrawableRes val image: Int, + val unit: String, + val inventory: Int, +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun OrderScreen() { + Scaffold( + topBar = { + TopAppBar(title = { Text("Orders") }, navigationIcon = { + IconButton(onClick = { }) { + Icon(Icons.Rounded.ArrowBack, null) + } + }) + }, + ) { + LazyColumn(Modifier.padding(it)) { + items(8) { + OrderItem() + } + } + } +} + +@Composable +fun OrderItem() { + Row( + Modifier + .fillMaxWidth() + .padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(id = R.drawable.s_3), + contentDescription = null, + modifier = Modifier.size(80.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Column { + Text("Shoes Pink", style = MaterialTheme.typography.titleMedium) + Text("Items: 3", style = MaterialTheme.typography.bodyMedium) + Text("Quantity: 1", style = MaterialTheme.typography.bodyMedium) + Text("Price: Rs. 1200", style = MaterialTheme.typography.bodyMedium) + Text("Delivery Status: Pending", style = MaterialTheme.typography.bodyMedium) + } + } +} + +private data class DottedShape( + val step: Dp, +) : Shape { + override fun createOutline( + size: Size, + layoutDirection: LayoutDirection, + density: Density + ) = Outline.Generic(Path().apply { + val stepPx = with(density) { step.toPx() } + val stepsCount = (size.height / stepPx).roundToInt() + val actualStep = size.height / stepsCount + val dotSize = Size(width = size.width, height = actualStep / 2) + for (i in 0 until stepsCount) { + addRect( + Rect( + offset = Offset(y = i * actualStep, x = 30f), + size = dotSize + ) + ) + } + close() + }) +} + +@Preview(showBackground = false) +@Composable +fun OrderTracking(modifier: Modifier = Modifier) { + Card(modifier.padding(8.dp)) { + Column(Modifier.padding(8.dp)) { + + Row(Modifier.fillMaxWidth()) { + Icon( + painter = painterResource(id = R.drawable.ic_location), + contentDescription = "null" + ) + Column { + Text("Pickup Point", fontWeight = FontWeight.Bold, fontSize = 16.sp) + Text("Zadeshwar", fontWeight = FontWeight.Light, fontSize = 14.sp) + } + } + Box( + Modifier + .width(2.dp) + .height(50.dp) + .background(Color.Gray, shape = DottedShape(step = 10.dp)) + ) + Row(Modifier.fillMaxWidth()) { + Icon( + painter = painterResource(id = R.drawable.ic_location), + contentDescription = "null" + ) + Column { + Text("Delivery Point", fontWeight = FontWeight.Bold, fontSize = 16.sp) + Text("Zadeshwar no.2", fontWeight = FontWeight.Light, fontSize = 14.sp) + } + } + + } + } +} diff --git a/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/AppNavHost.kt b/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/AppNavHost.kt index 0f37c05..189cee0 100644 --- a/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/AppNavHost.kt +++ b/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/AppNavHost.kt @@ -9,6 +9,7 @@ import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.navArgument +import com.kaushalvasava.app.composeui.screen.AuthenticationScreen import com.kaushalvasava.app.composeui.screen.ProductDetailScreen import com.kaushalvasava.app.composeui.screen.ProductsScreen @@ -16,7 +17,7 @@ import com.kaushalvasava.app.composeui.screen.ProductsScreen fun AppNavHost( modifier: Modifier = Modifier, navController: NavHostController, - startDestination: String = NavigationItem.PRODUCTS, + startDestination: String = NavigationItem.AUTHENTICATION, ) { NavHost( modifier = modifier, @@ -47,6 +48,9 @@ fun AppNavHost( ) } ) { + composable(NavigationItem.AUTHENTICATION) { + AuthenticationScreen(navController) + } composable(NavigationItem.PRODUCTS) { ProductsScreen(navController) } diff --git a/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/NavigationItem.kt b/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/NavigationItem.kt index 6f75455..57d2d0b 100644 --- a/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/NavigationItem.kt +++ b/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/NavigationItem.kt @@ -3,4 +3,5 @@ package com.kaushalvasava.app.composeui.ui.navigation object NavigationItem { const val PRODUCTS = "products" const val PRODUCT_DETAILS = "product_details" + const val AUTHENTICATION = "authentication" } \ No newline at end of file diff --git a/app/src/main/java/com/kaushalvasava/app/composeui/util/ValidUtil.kt b/app/src/main/java/com/kaushalvasava/app/composeui/util/ValidUtil.kt new file mode 100644 index 0000000..135e703 --- /dev/null +++ b/app/src/main/java/com/kaushalvasava/app/composeui/util/ValidUtil.kt @@ -0,0 +1,34 @@ +package com.kaushalvasava.app.composeui.util + +import android.util.Patterns +import java.util.regex.Pattern + +object ValidUtil { + + fun isValidEmail(email: String): Boolean { + val pattern: Pattern = Patterns.EMAIL_ADDRESS + return pattern.matcher(email).matches() + } + + fun isValidName(name: String): Boolean { + val pattern: Pattern = + Pattern.compile("^([a-zA-Z]{2,}\\s[a-zA-Z]+'?-?[a-zA-Z]{2,}\\s?([a-zA-Z]+)?)") + return pattern.matcher(name).matches() + } + + + fun isValidPasswordFormat(password: String): Boolean { + val passwordREGEX = Pattern.compile( + "^" + + "(?=.*[0-9])" + //at least 1 digit + "(?=.*[a-z])" + //at least 1 lower case letter + "(?=.*[A-Z])" + //at least 1 upper case letter + "(?=.*[a-zA-Z])" + //any letter + "(?=.*[@#$%^&+=])" + //at least 1 special character + "(?=\\S+$)" + //no white spaces + ".{10,}" + //at least 8 characters + "$" + ) + return passwordREGEX.matcher(password).matches() + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_facebook.xml b/app/src/main/res/drawable/ic_facebook.xml new file mode 100644 index 0000000..b60ee8a --- /dev/null +++ b/app/src/main/res/drawable/ic_facebook.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_google.xml b/app/src/main/res/drawable/ic_google.xml new file mode 100644 index 0000000..61448dd --- /dev/null +++ b/app/src/main/res/drawable/ic_google.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_location.xml b/app/src/main/res/drawable/ic_location.xml new file mode 100644 index 0000000..7eb4ae9 --- /dev/null +++ b/app/src/main/res/drawable/ic_location.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_visibility.xml b/app/src/main/res/drawable/ic_visibility.xml new file mode 100644 index 0000000..f843e29 --- /dev/null +++ b/app/src/main/res/drawable/ic_visibility.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_visibility_off.xml b/app/src/main/res/drawable/ic_visibility_off.xml new file mode 100644 index 0000000..5993ca3 --- /dev/null +++ b/app/src/main/res/drawable/ic_visibility_off.xml @@ -0,0 +1,5 @@ + + + + + From 19c01b3c877b3692baba1c3d9c1b6f8dc6d67d80 Mon Sep 17 00:00:00 2001 From: KaushalVasava Date: Sun, 7 Apr 2024 11:24:10 +0530 Subject: [PATCH 2/4] Add Orders and Order Tracking screens --- .../app/composeui/screen/OrderScreen.kt | 153 ++++-------- .../composeui/screen/OrderTrackingScreen.kt | 235 ++++++++++++++++++ .../composeui/screen/ProductDetailScreen.kt | 5 +- .../app/composeui/ui/navigation/AppNavHost.kt | 10 +- .../composeui/ui/navigation/NavigationItem.kt | 2 + 5 files changed, 296 insertions(+), 109 deletions(-) create mode 100644 app/src/main/java/com/kaushalvasava/app/composeui/screen/OrderTrackingScreen.kt diff --git a/app/src/main/java/com/kaushalvasava/app/composeui/screen/OrderScreen.kt b/app/src/main/java/com/kaushalvasava/app/composeui/screen/OrderScreen.kt index c9b38bc..8244eeb 100644 --- a/app/src/main/java/com/kaushalvasava/app/composeui/screen/OrderScreen.kt +++ b/app/src/main/java/com/kaushalvasava/app/composeui/screen/OrderScreen.kt @@ -1,75 +1,66 @@ package com.kaushalvasava.app.composeui.screen -import androidx.annotation.DrawableRes import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight 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.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ArrowBack -import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar -import androidx.compose.material3.VerticalDivider +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment 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.Color -import androidx.compose.ui.graphics.Outline -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.Shape 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.Density -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController import com.kaushalvasava.app.composeui.R -import kotlin.math.roundToInt - -data class Order( - val id: String, - // product id, and it's quantity - val items: Map, - val status: String, - val orderDate: Long, - val deliveryTime: Long, -) - -data class Product( - val id: String, - val name: String, - val price: String, - val minQuantity: Int, - val maxQuantity: Int, - @DrawableRes val image: Int, - val unit: String, - val inventory: Int, -) +import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) +@Preview(showBackground = false) @Composable -fun OrderScreen() { +fun OrderScreen(navController: NavController = rememberNavController()) { + + var isBottomSheet by remember { + mutableStateOf(false) + } + + val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val coroutineScope = rememberCoroutineScope() + + if (isBottomSheet) { + ModalBottomSheet( + sheetState = bottomSheetState, + onDismissRequest = { + coroutineScope.launch { + isBottomSheet = false + bottomSheetState.hide() + } + }) { + OrderTrackingScreen() + } + } Scaffold( topBar = { TopAppBar(title = { Text("Orders") }, navigationIcon = { @@ -81,17 +72,26 @@ fun OrderScreen() { ) { LazyColumn(Modifier.padding(it)) { items(8) { - OrderItem() + OrderItem { + coroutineScope.launch { + isBottomSheet = true + bottomSheetState.show() + } +// navController.navigate(NavigationItem.ORDER_TRACKING) + } } } } } @Composable -fun OrderItem() { +fun OrderItem(onClick: () -> Unit) { Row( Modifier .fillMaxWidth() + .clickable { + onClick() + } .padding(8.dp), verticalAlignment = Alignment.CenterVertically ) { @@ -102,72 +102,11 @@ fun OrderItem() { ) Spacer(modifier = Modifier.width(8.dp)) Column { - Text("Shoes Pink", style = MaterialTheme.typography.titleMedium) + Text("Shoes Green", style = MaterialTheme.typography.titleMedium) Text("Items: 3", style = MaterialTheme.typography.bodyMedium) Text("Quantity: 1", style = MaterialTheme.typography.bodyMedium) Text("Price: Rs. 1200", style = MaterialTheme.typography.bodyMedium) Text("Delivery Status: Pending", style = MaterialTheme.typography.bodyMedium) } } -} - -private data class DottedShape( - val step: Dp, -) : Shape { - override fun createOutline( - size: Size, - layoutDirection: LayoutDirection, - density: Density - ) = Outline.Generic(Path().apply { - val stepPx = with(density) { step.toPx() } - val stepsCount = (size.height / stepPx).roundToInt() - val actualStep = size.height / stepsCount - val dotSize = Size(width = size.width, height = actualStep / 2) - for (i in 0 until stepsCount) { - addRect( - Rect( - offset = Offset(y = i * actualStep, x = 30f), - size = dotSize - ) - ) - } - close() - }) -} - -@Preview(showBackground = false) -@Composable -fun OrderTracking(modifier: Modifier = Modifier) { - Card(modifier.padding(8.dp)) { - Column(Modifier.padding(8.dp)) { - - Row(Modifier.fillMaxWidth()) { - Icon( - painter = painterResource(id = R.drawable.ic_location), - contentDescription = "null" - ) - Column { - Text("Pickup Point", fontWeight = FontWeight.Bold, fontSize = 16.sp) - Text("Zadeshwar", fontWeight = FontWeight.Light, fontSize = 14.sp) - } - } - Box( - Modifier - .width(2.dp) - .height(50.dp) - .background(Color.Gray, shape = DottedShape(step = 10.dp)) - ) - Row(Modifier.fillMaxWidth()) { - Icon( - painter = painterResource(id = R.drawable.ic_location), - contentDescription = "null" - ) - Column { - Text("Delivery Point", fontWeight = FontWeight.Bold, fontSize = 16.sp) - Text("Zadeshwar no.2", fontWeight = FontWeight.Light, fontSize = 14.sp) - } - } - - } - } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/kaushalvasava/app/composeui/screen/OrderTrackingScreen.kt b/app/src/main/java/com/kaushalvasava/app/composeui/screen/OrderTrackingScreen.kt new file mode 100644 index 0000000..dab8f29 --- /dev/null +++ b/app/src/main/java/com/kaushalvasava/app/composeui/screen/OrderTrackingScreen.kt @@ -0,0 +1,235 @@ +package com.kaushalvasava.app.composeui.screen + +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.Row +import androidx.compose.foundation.layout.Spacer +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.rounded.Person +import androidx.compose.material.icons.rounded.Phone +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +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.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +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.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.kaushalvasava.app.composeui.R +import kotlin.math.roundToInt + +@Preview(showBackground = true) +@Composable +fun OrderTrackingScreen() { + var isDelivered by remember { + mutableStateOf(false) + } + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(horizontal = 8.dp) + ) { + Row( + Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(id = R.drawable.s_3), + contentDescription = null, + modifier = Modifier.size(60.dp) + ) + Column { + Text("Shoes S2 - Green", fontWeight = FontWeight.Bold) + Text("Quantity: 1", fontSize = 14.sp) + } + } + OrderTrackingCard { + isDelivered + } + Text( + "Related Information", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold + ) + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + Icons.Rounded.Person, + contentDescription = "", + modifier = Modifier + .clip(CircleShape) + .background(Color.Green.copy(0.2f)) + .size(50.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Column { + Text("Kaushal V.") + Text("Courrier") + } + Spacer(modifier = Modifier.weight(1f)) + IconButton( + onClick = { + //do call to driver + }, + modifier = Modifier + .clip(CircleShape) + .background(Color.Green.copy(0.2f)) + ) { + Icon(Icons.Rounded.Phone, contentDescription = "call") + } + } + Button( + onClick = { + isDelivered = !isDelivered + }, + modifier = Modifier + .padding(bottom = 16.dp) + .fillMaxWidth() + .clip(RoundedCornerShape(16.dp)) + .drawBehind { + drawRect( + Brush.linearGradient( + listOf(Color.Blue, Color.Green) + ) + ) + } + .align(Alignment.CenterHorizontally), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = Color.White + ), + shape = RoundedCornerShape(16.dp) + ) { + Text( + "Pay Now Rs. 1200", + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.SemiBold + ) + } + } +} + +@Preview(showBackground = false) +@Composable +fun OrderTrackingCard(modifier: Modifier = Modifier, isDelivered: () -> Boolean = { false }) { + Card( + modifier, + colors = CardDefaults.cardColors( + containerColor = Color.Green.copy(0.2f) + ) + ) { + Column(Modifier.padding(8.dp)) { + Row(Modifier.fillMaxWidth()) { + Icon( + painter = painterResource(id = R.drawable.ic_location), + contentDescription = "null" + ) + Column(Modifier.weight(1f)) { + Text("Pickup Point", fontWeight = FontWeight.Bold, fontSize = 16.sp) + Text( + "Shop no.16 K Store, Zadeshwar ", + maxLines = 2, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.Light, + fontSize = 14.sp + ) + } + Column(horizontalAlignment = Alignment.End) { + Text("Order Time", fontWeight = FontWeight.Bold, fontSize = 16.sp) + Text("12 pm", fontWeight = FontWeight.Light, fontSize = 14.sp) + } + } + Box( + Modifier + .width(4.dp) + .height(80.dp) + .background( + color = if (isDelivered()) { + Color(0xFF16AB1D) + } else + Color.Gray, + shape = DottedShape(step = 15.dp) + ) + ) + Row(Modifier.fillMaxWidth()) { + Icon( + painter = painterResource(id = R.drawable.ic_location), + contentDescription = "null" + ) + Column { + Text("Delivery Point", fontWeight = FontWeight.Bold, fontSize = 16.sp) + Text("Zadeshwar no.2", fontWeight = FontWeight.Light, fontSize = 14.sp) + } + Spacer(Modifier.weight(1f)) + Column(horizontalAlignment = Alignment.End) { + Text("Delivery Time", fontWeight = FontWeight.Bold, fontSize = 16.sp) + Text("3 pm", fontWeight = FontWeight.Light, fontSize = 14.sp) + } + } + } + } +} + + +private data class DottedShape( + val step: Dp, +) : Shape { + override fun createOutline( + size: Size, + layoutDirection: LayoutDirection, + density: Density, + ) = Outline.Generic(Path().apply { + val stepPx = with(density) { step.toPx() } + val stepsCount = (size.height / stepPx).roundToInt() + val actualStep = size.height / stepsCount + val dotSize = Size(width = size.width, height = actualStep / 2) + for (i in 0 until stepsCount) { + addRect( + Rect( + offset = Offset(y = i * actualStep, x = 30f), + size = dotSize + ) + ) + } + close() + }) +} diff --git a/app/src/main/java/com/kaushalvasava/app/composeui/screen/ProductDetailScreen.kt b/app/src/main/java/com/kaushalvasava/app/composeui/screen/ProductDetailScreen.kt index f58c451..4e9d90e 100644 --- a/app/src/main/java/com/kaushalvasava/app/composeui/screen/ProductDetailScreen.kt +++ b/app/src/main/java/com/kaushalvasava/app/composeui/screen/ProductDetailScreen.kt @@ -61,6 +61,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController +import com.kaushalvasava.app.composeui.ui.navigation.NavigationItem import kotlinx.coroutines.delay @Preview @@ -373,7 +374,9 @@ fun ProductDetailScreen( colors = ButtonDefaults.buttonColors( containerColor = Color.Blue ), - onClick = {} + onClick = { + navController.navigate(NavigationItem.ORDERS) + } ) { Icon(Icons.Rounded.ShoppingCart, contentDescription = null, tint = Color.White) Text("Add to Cart") diff --git a/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/AppNavHost.kt b/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/AppNavHost.kt index 189cee0..528e499 100644 --- a/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/AppNavHost.kt +++ b/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/AppNavHost.kt @@ -10,6 +10,8 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.navArgument import com.kaushalvasava.app.composeui.screen.AuthenticationScreen +import com.kaushalvasava.app.composeui.screen.OrderScreen +import com.kaushalvasava.app.composeui.screen.OrderTrackingScreen import com.kaushalvasava.app.composeui.screen.ProductDetailScreen import com.kaushalvasava.app.composeui.screen.ProductsScreen @@ -17,7 +19,7 @@ import com.kaushalvasava.app.composeui.screen.ProductsScreen fun AppNavHost( modifier: Modifier = Modifier, navController: NavHostController, - startDestination: String = NavigationItem.AUTHENTICATION, + startDestination: String = NavigationItem.ORDERS, ) { NavHost( modifier = modifier, @@ -63,5 +65,11 @@ fun AppNavHost( if (productId != null) ProductDetailScreen(productId = productId, navController = navController) } + composable(NavigationItem.ORDERS) { + OrderScreen(navController) + } + composable(NavigationItem.ORDER_TRACKING) { + OrderTrackingScreen() + } } } \ No newline at end of file diff --git a/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/NavigationItem.kt b/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/NavigationItem.kt index 57d2d0b..302f233 100644 --- a/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/NavigationItem.kt +++ b/app/src/main/java/com/kaushalvasava/app/composeui/ui/navigation/NavigationItem.kt @@ -4,4 +4,6 @@ object NavigationItem { const val PRODUCTS = "products" const val PRODUCT_DETAILS = "product_details" const val AUTHENTICATION = "authentication" + const val ORDERS = "orders" + const val ORDER_TRACKING = "order_tracking" } \ No newline at end of file From a0a007f6eeb26c5f9f709f650e3f420d9f5f37ee Mon Sep 17 00:00:00 2001 From: Kaushal Vasava <49050597+KaushalVasava@users.noreply.github.com> Date: Sun, 7 Apr 2024 11:31:24 +0530 Subject: [PATCH 3/4] Create README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..86981f5 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +You can create you custom shape to develop such type UIs. + +# Screenshot +![Screenshot_order1 (1)](https://github.com/KaushalVasava/ComposeUI/assets/49050597/06e0b81c-1932-4b02-86e2-3cd90d088c35) + +# Video +https://github.com/KaushalVasava/ComposeUI/assets/49050597/412e1e1f-45cb-4bad-a368-259ef343b656 From 602196b5ef819fced47b5bb46e82d716d895d995 Mon Sep 17 00:00:00 2001 From: Kaushal Vasava <49050597+KaushalVasava@users.noreply.github.com> Date: Sun, 7 Apr 2024 11:31:56 +0530 Subject: [PATCH 4/4] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 86981f5..ee6b882 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +# Order Tracking UI You can create you custom shape to develop such type UIs. # Screenshot