diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b98a78c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,12 @@
+
+
+# Screenshots
+
+
+
+
+# Video
+
+
+https://github.com/KaushalVasava/ComposeUI/assets/49050597/069357c0-041c-49a9-9dcc-117c91605698
+
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..d3c4402
--- /dev/null
+++ b/app/src/main/java/com/kaushalvasava/app/composeui/screen/AuthenticationScreen.kt
@@ -0,0 +1,400 @@
+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 = {
+ // google auth
+ }, 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 = {
+ // facebook auth
+ }, 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/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_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 @@
+
+
+
+
+