From 7c818589ec6afaf1388d06b4e6b9ffbb8d68ad93 Mon Sep 17 00:00:00 2001 From: Ahmed Date: Tue, 7 Jan 2025 20:52:18 +0200 Subject: [PATCH] Implemented Firebase Authentication and optimized some screens --- .../com/example/finjan/NavigationManager.kt | 11 +- .../java/com/example/finjan/model/PageItem.kt | 6 +- .../com/example/finjan/ui/Uicomponents.kt | 8 +- .../ui/screens/authentication/SignInScreen.kt | 61 ++--- .../ui/screens/authentication/SignUpScreen.kt | 88 ++++---- .../finjan/ui/screens/home/ProfileScreen.kt | 7 +- .../ui/screens/welcome/PageViewScreen.kt | 213 +++++++++--------- .../viewmodel/AuthenticationViewModel.kt | 85 +++++++ .../finjan/viewmodel/LoginViewModel.kt | 42 ---- 9 files changed, 296 insertions(+), 225 deletions(-) create mode 100644 app/src/main/java/com/example/finjan/viewmodel/AuthenticationViewModel.kt delete mode 100644 app/src/main/java/com/example/finjan/viewmodel/LoginViewModel.kt diff --git a/app/src/main/java/com/example/finjan/NavigationManager.kt b/app/src/main/java/com/example/finjan/NavigationManager.kt index d5e50f7..f998ce3 100644 --- a/app/src/main/java/com/example/finjan/NavigationManager.kt +++ b/app/src/main/java/com/example/finjan/NavigationManager.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.Composable import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable -import com.example.finjan.ui.screens.authentication.LoginScreen +import com.example.finjan.ui.screens.authentication.SignInScreen import com.example.finjan.ui.screens.authentication.SignUpScreen import com.example.finjan.ui.screens.welcome.PageViewScreen import com.example.finjan.ui.screens.welcome.WelcomeScreen @@ -14,7 +14,7 @@ import com.example.finjan.ui.screens.home.OffersScreen import com.example.finjan.ui.screens.home.ProfileScreen import com.example.finjan.ui.screens.settings.SettingsScreen import com.example.finjan.ui.screens.welcome.SplashScreen -import com.example.finjan.viewmodel.LoginViewModel +import com.example.finjan.viewmodel.AuthenticationViewModel import com.example.finjan.viewmodel.SharedViewModel @Composable @@ -36,11 +36,12 @@ fun NavigationManager( WelcomeScreen(navController) } composable("login_screen") { - val loginViewModel = androidx.lifecycle.viewmodel.compose.viewModel() - LoginScreen(navController, loginViewModel) + val loginViewModel = androidx.lifecycle.viewmodel.compose.viewModel() + SignInScreen(navController, loginViewModel) } composable("signup_screen") { - SignUpScreen(navController) + val loginViewModel = androidx.lifecycle.viewmodel.compose.viewModel() + SignUpScreen(navController, loginViewModel) } composable("home") { HomeScreen(navController) diff --git a/app/src/main/java/com/example/finjan/model/PageItem.kt b/app/src/main/java/com/example/finjan/model/PageItem.kt index 238684b..b71aef0 100644 --- a/app/src/main/java/com/example/finjan/model/PageItem.kt +++ b/app/src/main/java/com/example/finjan/model/PageItem.kt @@ -1,3 +1,7 @@ package com.example.finjan.model -data class PageItem(val image: Int, val title: String, val subTitle: String) \ No newline at end of file +data class PageItem( + val image: Int, + val title: String, + val subtitle: String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/finjan/ui/Uicomponents.kt b/app/src/main/java/com/example/finjan/ui/Uicomponents.kt index caed39f..49a1e06 100644 --- a/app/src/main/java/com/example/finjan/ui/Uicomponents.kt +++ b/app/src/main/java/com/example/finjan/ui/Uicomponents.kt @@ -71,7 +71,6 @@ import com.airbnb.lottie.compose.LottieConstants import com.airbnb.lottie.compose.animateLottieCompositionAsState import com.airbnb.lottie.compose.rememberLottieComposition import com.example.finjan.R -import com.example.finjan.ui.screens.welcome.primaryFontColor import com.example.finjan.ui.theme.BackgroundColor import com.example.finjan.ui.theme.PoppinsFontFamily import com.example.finjan.ui.theme.PrimaryColor @@ -220,7 +219,7 @@ fun AppTextField( @Composable -fun Footer(text: String, textButton: String, onClick: @Composable () -> Unit, function: @Composable () -> Unit) { +fun Footer(text: String, textButton: String, onClick: () -> Unit, function: @Composable () -> Unit) { Row( Modifier .fillMaxWidth() @@ -236,7 +235,7 @@ fun Footer(text: String, textButton: String, onClick: @Composable () -> Unit, fu fontFamily = PoppinsFontFamily ) ) - TextButton(onClick = { onClick }) { + TextButton(onClick = { onClick() }) { // Invoke onClick() here Text( textButton, style = TextStyle( @@ -250,6 +249,7 @@ fun Footer(text: String, textButton: String, onClick: @Composable () -> Unit, fu } } + @Composable fun SplashScreen() { val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.splash_screen)) @@ -346,7 +346,7 @@ fun SearchBar() { ), singleLine = true, textStyle = TextStyle( - color = primaryFontColor, + color = PrimaryColor, fontSize = 15.sp, fontFamily = PoppinsFontFamily ), diff --git a/app/src/main/java/com/example/finjan/ui/screens/authentication/SignInScreen.kt b/app/src/main/java/com/example/finjan/ui/screens/authentication/SignInScreen.kt index 1a07237..879e65a 100644 --- a/app/src/main/java/com/example/finjan/ui/screens/authentication/SignInScreen.kt +++ b/app/src/main/java/com/example/finjan/ui/screens/authentication/SignInScreen.kt @@ -19,20 +19,21 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.example.finjan.ui.AppTextField +import com.example.finjan.ui.BorderButton import com.example.finjan.ui.FilledButton import com.example.finjan.ui.Logo import com.example.finjan.ui.theme.BackgroundColor import com.example.finjan.ui.theme.PoppinsFontFamily import com.example.finjan.ui.theme.PrimaryColor -import com.example.finjan.viewmodel.LoginViewModel +import com.example.finjan.viewmodel.AuthenticationViewModel import com.example.finjan.ui.theme.FinjanTheme @Composable -fun LoginScreen(navController: NavController, loginViewModel: LoginViewModel) { +fun SignInScreen(navController: NavController, authViewModel: AuthenticationViewModel) { FinjanTheme { - // Observe the error message from the ViewModel - val errorMessage = loginViewModel.errorMessage + val errorMessage = authViewModel.errorMessage + val isLoading = authViewModel.isLoading Column( modifier = Modifier @@ -41,7 +42,11 @@ fun LoginScreen(navController: NavController, loginViewModel: LoginViewModel) { verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Logo(modifier = Modifier) + + Logo(modifier = Modifier + .align(Alignment.CenterHorizontally) + ) + Text( text = "Welcome Back!", style = TextStyle( @@ -51,43 +56,47 @@ fun LoginScreen(navController: NavController, loginViewModel: LoginViewModel) { color = PrimaryColor ) ) + Spacer(modifier = Modifier.height(45.dp)) AppTextField( hint = "Email", - value = loginViewModel.email, // Bind to ViewModel state - onValueChange = { input -> // Validate and update ViewModel state - loginViewModel.email = input // Update email in ViewModel - loginViewModel.isEmailValid = loginViewModel.isEmailValid(input) // Validate email + value = authViewModel.email, + onValueChange = { input -> + authViewModel.email = input + authViewModel.isEmailValid = authViewModel.isEmailValid(input) }, - keyboardType = KeyboardType.Email // Email-specific keyboard + keyboardType = KeyboardType.Email ) + Spacer(modifier = Modifier.height(28.dp)) AppTextField( hint = "Password", - value = loginViewModel.password, - onValueChange = { input -> // Validate and update ViewModel state - loginViewModel.password = input - loginViewModel.isPasswordValid = loginViewModel.isPasswordValid(input) + value = authViewModel.password, + onValueChange = { input -> + authViewModel.password = input + authViewModel.isPasswordValid = authViewModel.isPasswordValid(input) }, keyboardType = KeyboardType.Password ) + Spacer(modifier = Modifier.height(28.dp)) FilledButton( + modifier = Modifier.padding(horizontal = 34.dp), onClick = { - if (loginViewModel.authenticate()) { + authViewModel.signIn { navController.navigate("home") } }, - text = "Login", - modifier = Modifier.padding(horizontal = 34.dp) + text = if (isLoading) "Logging In..." else "Login" ) - // Display the error message in a Snackbar or Text + Spacer(modifier = Modifier.height(16.dp)) + + // Display the error message if (errorMessage.isNotEmpty()) { - Spacer(modifier = Modifier.height(16.dp)) Text( text = errorMessage, style = TextStyle(color = androidx.compose.ui.graphics.Color.Red) @@ -96,13 +105,13 @@ fun LoginScreen(navController: NavController, loginViewModel: LoginViewModel) { Spacer(modifier = Modifier.height(8.dp)) - // Hidden back button - OutlinedButton (onClick = { - // Navigate back to the previous screen - navController.popBackStack() - }) { - Text(text = "Back") - } + BorderButton( + modifier = Modifier.padding(horizontal = 100.dp), + text = "Sign Up", + onClick = { + navController.navigate("signup_screen") + } + ) } } } diff --git a/app/src/main/java/com/example/finjan/ui/screens/authentication/SignUpScreen.kt b/app/src/main/java/com/example/finjan/ui/screens/authentication/SignUpScreen.kt index 6f1e0a9..0be1098 100644 --- a/app/src/main/java/com/example/finjan/ui/screens/authentication/SignUpScreen.kt +++ b/app/src/main/java/com/example/finjan/ui/screens/authentication/SignUpScreen.kt @@ -10,7 +10,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier 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.unit.dp import androidx.compose.ui.unit.sp @@ -18,30 +17,41 @@ import androidx.navigation.NavController import com.example.finjan.ui.AppTextField import com.example.finjan.ui.FilledButton import com.example.finjan.ui.Footer +import com.example.finjan.ui.Logo import com.example.finjan.ui.theme.BackgroundColor import com.example.finjan.ui.theme.FinjanTheme import com.example.finjan.ui.theme.PoppinsFontFamily import com.example.finjan.ui.theme.PrimaryColor +import com.example.finjan.viewmodel.AuthenticationViewModel @Composable -fun SignUpScreen(navController: NavController) { +fun SignUpScreen(navController: NavController, loginViewModel: AuthenticationViewModel) { FinjanTheme { // State variables for the form fields var username by remember { mutableStateOf("") } var email by remember { mutableStateOf("") } - var mobileNumber by remember { mutableStateOf("") } - var address by remember { mutableStateOf("") } var password by remember { mutableStateOf("") } var confirmPassword by remember { mutableStateOf("") } + val errorMessage = loginViewModel.errorMessage + val isLoading = loginViewModel.isLoading + Column( modifier = Modifier .fillMaxWidth() + .fillMaxHeight() .background(BackgroundColor) .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally ) { - Spacer(modifier = Modifier.height(60.dp)) + + Logo( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + .align(Alignment.CenterHorizontally) + ) + Text( text = "Sign Up", style = TextStyle( @@ -51,73 +61,77 @@ fun SignUpScreen(navController: NavController) { color = PrimaryColor ) ) - Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(36.dp)) - // Updated AppTextFields AppTextField( hint = "Name", value = username, onValueChange = { username = it } ) + Spacer(modifier = Modifier.height(28.dp)) AppTextField( hint = "Email", value = email, - onValueChange = { email = it }, + onValueChange = { + email = it + loginViewModel.email = it + }, keyboardType = KeyboardType.Email ) - Spacer(modifier = Modifier.height(28.dp)) - AppTextField( - hint = "Mobile Number", - value = mobileNumber, - onValueChange = { mobileNumber = it }, - keyboardType = KeyboardType.Phone - ) - Spacer(modifier = Modifier.height(28.dp)) - - AppTextField( - hint = "Address", - value = address, - onValueChange = { address = it } - ) Spacer(modifier = Modifier.height(28.dp)) AppTextField( hint = "Password", value = password, - onValueChange = { password = it }, + onValueChange = { + password = it + loginViewModel.password = it + }, keyboardType = KeyboardType.Password ) + Spacer(modifier = Modifier.height(28.dp)) AppTextField( hint = "Confirm password", value = confirmPassword, onValueChange = { confirmPassword = it }, - keyboardType = KeyboardType.Password, - action = ImeAction.Done + keyboardType = KeyboardType.Password ) + Spacer(modifier = Modifier.height(28.dp)) // Sign-Up Button FilledButton( onClick = { if (password != confirmPassword) { - // Display error message if passwords don't match - println("Passwords do not match!") + loginViewModel.errorMessage = "Passwords do not match!" + } else if (username.isBlank()) { + loginViewModel.errorMessage = "Name cannot be empty!" } else { - navController.navigate("home") + loginViewModel.signUp(username) { + navController.navigate("home") + } } }, - text = "Sign Up", - modifier = Modifier - .padding(horizontal = 34.dp) + text = if (isLoading) "Signing Up..." else "Sign Up", + modifier = Modifier.padding(horizontal = 34.dp) ) + Spacer(modifier = Modifier.height(16.dp)) + + // Error Message + if (errorMessage.isNotEmpty()) { + Text( + text = errorMessage, + style = TextStyle(color = androidx.compose.ui.graphics.Color.Red) + ) + } + Spacer(modifier = Modifier.height(28.dp)) Footer( @@ -127,16 +141,6 @@ fun SignUpScreen(navController: NavController) { ) { Text(text = "Sign Up") } - - Spacer(modifier = Modifier.height(8.dp)) - - // Hidden back button - OutlinedButton (onClick = { - // Navigate back to the previous screen - navController.popBackStack() - }) { - Text(text = "Back") - } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/finjan/ui/screens/home/ProfileScreen.kt b/app/src/main/java/com/example/finjan/ui/screens/home/ProfileScreen.kt index 6c017e6..e2da2ec 100644 --- a/app/src/main/java/com/example/finjan/ui/screens/home/ProfileScreen.kt +++ b/app/src/main/java/com/example/finjan/ui/screens/home/ProfileScreen.kt @@ -24,6 +24,7 @@ import com.example.finjan.ui.theme.AccentColor import com.example.finjan.ui.theme.BackgroundColor import com.example.finjan.ui.theme.PoppinsFontFamily import com.example.finjan.ui.theme.PrimaryColor +import com.google.firebase.auth.FirebaseAuth @Composable fun ProfileScreen(navController: NavController) { @@ -98,12 +99,16 @@ fun ProfileScreen(navController: NavController) { FilledButton( onClick = { - /*TODO*/ + FirebaseAuth.getInstance().signOut() // Log out the user + navController.navigate("welcome_screen") { // Navigate to WelcomeScreen + popUpTo(0) // Clear the navigation stack + } }, text = "Logout", modifier = Modifier.padding(horizontal = 60.dp) ) + // Spacer for spacing the bottom navigation bar Spacer(modifier = Modifier.weight(1f)) diff --git a/app/src/main/java/com/example/finjan/ui/screens/welcome/PageViewScreen.kt b/app/src/main/java/com/example/finjan/ui/screens/welcome/PageViewScreen.kt index 1ba6c4a..ee7563e 100644 --- a/app/src/main/java/com/example/finjan/ui/screens/welcome/PageViewScreen.kt +++ b/app/src/main/java/com/example/finjan/ui/screens/welcome/PageViewScreen.kt @@ -1,165 +1,170 @@ package com.example.finjan.ui.screens.welcome +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController +import com.example.finjan.R import com.example.finjan.model.PageItem import com.example.finjan.ui.BorderButton import com.example.finjan.ui.FilledButton -import com.example.finjan.ui.theme.PrimaryColor -import kotlinx.coroutines.launch -import com.example.finjan.R import com.example.finjan.ui.theme.BackgroundColor import com.example.finjan.ui.theme.PoppinsFontFamily - -val PoppinsFontFamily = FontFamily.Default -val primaryFontColor = Color.Black -val secondaryFontColor = Color.Gray +import com.example.finjan.ui.theme.PrimaryColor +import com.example.finjan.ui.theme.SecondaryColor +import kotlinx.coroutines.launch @Composable fun PageViewScreen(navController: NavController) { - // Array of PageItem objects representing each page in the onboarding screen - val list = arrayOf( + val pages = listOf( PageItem( image = R.drawable.brewed_coffee, title = "Brewed to Perfection", - subTitle = "Discover the best Coffee you could taste" + subtitle = "Discover the best Coffee you could taste" ), PageItem( image = R.drawable.green_takeaway, title = "Naturally harvested", - subTitle = "High Quality GMO-Free Beans" + subtitle = "High Quality GMO-Free Beans" ), PageItem( image = R.drawable.heart_coffee, title = "Warm Coffee", - subTitle = "At the palm of your hands" + subtitle = "At the palm of your hands" ) ) - // Pager state to manage the horizontal pager - val pagerState = rememberPagerState(pageCount = { list.size }) - - // Coroutine scope for animations or asynchronous actions + val pagerState = rememberPagerState(pageCount = { pages.size }) val scope = rememberCoroutineScope() - // HorizontalPager to display the onboarding screens - HorizontalPager( - state = pagerState, + Column( modifier = Modifier .fillMaxSize() .background(BackgroundColor) - ) { index -> // "index" represents the currently displayed page - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxSize() - ) { - // Display the image for the current page - Image( - painter = painterResource(id = list[index].image), - contentDescription = null // No content description for decorative images - ) - Spacer(modifier = Modifier.height(30.dp)) - - // Display indicators to represent the current page position - Indicator(count = list.size, index = index) - - Spacer(modifier = Modifier.height(35.dp)) - - // Display the title of the current page - Text( - text = list[index].title, - style = TextStyle( - fontSize = 28.sp, - fontFamily = PoppinsFontFamily, - color = primaryFontColor + ) { + HorizontalPager( + state = pagerState, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { page -> + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Image( + painter = painterResource(id = pages[page].image), + contentDescription = null, + modifier = Modifier.padding(32.dp) ) - ) - Spacer(modifier = Modifier.height(33.dp)) - // Display the subtitle of the current page - Text( - text = list[index].subTitle, - style = TextStyle( - fontSize = 13.sp, - fontFamily = PoppinsFontFamily, - color = secondaryFontColor, + Text( + text = pages[page].title, + style = TextStyle( + fontSize = 28.sp, + color = PrimaryColor + ), textAlign = TextAlign.Center - ), - modifier = Modifier.padding(horizontal = 45.dp) - ) - Spacer(modifier = Modifier.height(40.dp)) + ) - FilledButton( - modifier = Modifier.padding(horizontal = 34.dp), - text = if (index < list.size - 1) "Next" else "Let's go" // Button text based on the page index - ) { - scope.launch { - if (index < list.size - 1) { - // Navigate to the next page if not the last page - pagerState.animateScrollToPage(index + 1) - } else { - // Navigate to the welcome screen if on the last page - navController.navigate("welcome_screen") - } - } + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = pages[page].subtitle, + style = TextStyle( + fontSize = 16.sp, + color = SecondaryColor + ), + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = 32.dp) + ) } + } - Spacer(modifier = Modifier.height(20.dp)) + // Page indicators + Row( + modifier = Modifier + .padding(bottom = 32.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + repeat(pages.size) { index -> + Box( + modifier = Modifier + .padding(4.dp) + .size(8.dp) + .clip(CircleShape) + .background( + if (pagerState.currentPage == index) + PrimaryColor + else + Color.LightGray + ) + ) + } + } - // Display the "Skip" button on all pages except the last one - if (index < list.size - 1) { - BorderButton( - modifier = Modifier.padding(horizontal = 34.dp), - text = "Skip", - color = secondaryFontColor - ) { - // Navigate to the welcome screen when "Skip" is clicked + // Buttons + FilledButton( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp), + text = if (pagerState.currentPage == pages.size - 1) "Get Started" else "Next" + ) { + scope.launch { + if (pagerState.currentPage < pages.size - 1) { + pagerState.animateScrollToPage(pagerState.currentPage + 1) + } else { navController.navigate("welcome_screen") } - Spacer(modifier = Modifier.height(20.dp)) } } - } -} -@Composable -fun Indicator(count: Int, index: Int) { - // Row of indicators representing the pages - Row( - modifier = Modifier - .fillMaxWidth() - .height(10.dp), // Fixed height for the row - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - for (i in 0 until count) { - // Circular indicator for each page - Box( + if (pagerState.currentPage < pages.size - 1) { + Spacer(modifier = Modifier.height(8.dp)) + + BorderButton( modifier = Modifier - .size(8.dp) - .clip(CircleShape) - .background(if (i == index) PrimaryColor else Color.LightGray) // Highlight the active indicator - ) - Spacer(modifier = Modifier.size(5.dp)) + .fillMaxWidth() + .padding(horizontal = 32.dp), + text = "Skip", + color = SecondaryColor + ) { + navController.navigate("welcome_screen") + } } + + Spacer(modifier = Modifier.height(32.dp)) } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/finjan/viewmodel/AuthenticationViewModel.kt b/app/src/main/java/com/example/finjan/viewmodel/AuthenticationViewModel.kt new file mode 100644 index 0000000..bc2d48c --- /dev/null +++ b/app/src/main/java/com/example/finjan/viewmodel/AuthenticationViewModel.kt @@ -0,0 +1,85 @@ +package com.example.finjan.viewmodel + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException +import com.google.firebase.auth.FirebaseAuthInvalidUserException +import com.google.firebase.auth.UserProfileChangeRequest + +class AuthenticationViewModel : ViewModel() { + + private val auth: FirebaseAuth = FirebaseAuth.getInstance() + + var email by mutableStateOf("") + var password by mutableStateOf("") + var errorMessage by mutableStateOf("") + var isLoading by mutableStateOf(false) + + var isEmailValid by mutableStateOf(true) + var isPasswordValid by mutableStateOf(true) + + fun isEmailValid(email: String): Boolean { + val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$" + return email.matches(emailRegex.toRegex()) + } + + fun isPasswordValid(password: String): Boolean { + val passwordRegex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@\$!%*?&#])[A-Za-z\\d@\$!%*?&#]{8,}$" + return password.matches(passwordRegex.toRegex()) + } + + // Firebase Authentication for sign-in + fun signIn(onSuccess: () -> Unit) { + isLoading = true + auth.signInWithEmailAndPassword(email.trim(), password) + .addOnCompleteListener { task -> + isLoading = false + if (task.isSuccessful) { + errorMessage = "" + onSuccess() + } else { + val exception = task.exception + errorMessage = when (exception) { + is FirebaseAuthInvalidUserException -> "No account found with this email." + is FirebaseAuthInvalidCredentialsException -> { + password = "" // Clear the password field + "Incorrect password. Please try again." + } + else -> exception?.localizedMessage ?: "Sign-in failed. Please check your credentials." + } + } + } + + } + + // Firebase Authentication for sign-up + fun signUp(username: String, onSuccess: () -> Unit) { + isLoading = true + auth.createUserWithEmailAndPassword(email.trim(), password) + .addOnCompleteListener { task -> + if (task.isSuccessful) { + // Update user profile with username + val user = auth.currentUser + val profileUpdates = UserProfileChangeRequest.Builder() + .setDisplayName(username) + .build() + + user?.updateProfile(profileUpdates)?.addOnCompleteListener { updateTask -> + isLoading = false + if (updateTask.isSuccessful) { + errorMessage = "" + onSuccess() + } else { + errorMessage = updateTask.exception?.localizedMessage ?: "Profile update failed." + } + } + } else { + isLoading = false + errorMessage = task.exception?.localizedMessage ?: "Sign-up failed." + } + } + } +} diff --git a/app/src/main/java/com/example/finjan/viewmodel/LoginViewModel.kt b/app/src/main/java/com/example/finjan/viewmodel/LoginViewModel.kt deleted file mode 100644 index 4d7fa81..0000000 --- a/app/src/main/java/com/example/finjan/viewmodel/LoginViewModel.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.example.finjan.viewmodel - -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.lifecycle.ViewModel - -class LoginViewModel : ViewModel() { - private val validCredentials = mapOf( - "ahmed@gmail.com" to "FinjannA", - "noureen@gmail.com" to "FinjannN", - "shahd@gmail.com" to "FinjannS" - ) - - var email by mutableStateOf("") - var password by mutableStateOf("") - var errorMessage by mutableStateOf("") - var isEmailValid by mutableStateOf(true) // Tracks email validation state - var isPasswordValid by mutableStateOf(true) // Tracks password validation state - - fun isEmailValid(email: String): Boolean { - val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$" - return email.matches(emailRegex.toRegex()) - } - - fun isPasswordValid(password: String): Boolean { - // Password regex: at least 8 characters, one uppercase, one lowercase, one digit, one special character - val passwordRegex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@\$!%*?&#])[A-Za-z\\d@\$!%*?&#]{8,}$" - return password.matches(passwordRegex.toRegex()) - } - - fun authenticate(): Boolean { - val username = email.trim().lowercase() - val userPassword = password - return if (validCredentials[username] == userPassword) { - true - } else { - errorMessage = "Invalid input" - false - } - } -}