LoginScreen
This commit is contained in:
@@ -1,14 +1,22 @@
|
|||||||
package `in`.sminnovations.attendanceapp.screens.login.ui
|
package `in`.sminnovations.attendanceapp.screens.login.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Lock
|
||||||
|
import androidx.compose.material.icons.filled.Person
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -36,23 +44,23 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import `in`.sminnovations.attendanceapp.R
|
import `in`.sminnovations.attendanceapp.R
|
||||||
|
import `in`.sminnovations.attendanceapp.screens.login.ui.componets.OutlinedTextFieldWithError
|
||||||
import `in`.sminnovations.attendanceapp.screens.login.viewModel.LoginViewModel
|
import `in`.sminnovations.attendanceapp.screens.login.viewModel.LoginViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreen(
|
fun LoginScreen(
|
||||||
loginViewModel: LoginViewModel = hiltViewModel(),
|
loginViewModel: LoginViewModel = hiltViewModel(),
|
||||||
onLoginSuccess: () -> Unit
|
onLoginSuccess: () -> Unit
|
||||||
) {
|
) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val isLoading by loginViewModel.isLoading.collectAsState()
|
val isPasswordError by loginViewModel.isPasswordError.collectAsState()
|
||||||
|
val isUserIdError by loginViewModel.isIdError.collectAsState()
|
||||||
val isLoggedIn by loginViewModel.isLoggedIn.collectAsState()
|
val isLoggedIn by loginViewModel.isLoggedIn.collectAsState()
|
||||||
|
|
||||||
var userId by remember { mutableStateOf(TextFieldValue("")) }
|
var userId by remember { mutableStateOf("") }
|
||||||
var password by remember { mutableStateOf(TextFieldValue("")) }
|
var password by remember { mutableStateOf("") }
|
||||||
var passwordVisible by remember { mutableStateOf(false) }
|
var passwordVisible by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// Navigate to the main screen if the user is already logged in
|
|
||||||
LaunchedEffect(isLoggedIn) {
|
LaunchedEffect(isLoggedIn) {
|
||||||
if (isLoggedIn) onLoginSuccess()
|
if (isLoggedIn) onLoginSuccess()
|
||||||
}
|
}
|
||||||
@@ -60,81 +68,88 @@ fun LoginScreen(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(26.dp),
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(horizontal = 16.dp, vertical = 24.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(text = "Attendance App", style = MaterialTheme.typography.headlineLarge)
|
Text(text = "Attendance App", style = MaterialTheme.typography.headlineLarge)
|
||||||
Spacer(modifier = Modifier.height(50.dp))
|
Spacer(modifier = Modifier.height(40.dp))
|
||||||
|
|
||||||
// Logo Image
|
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.drawable.smi_logo),
|
painter = painterResource(id = R.drawable.smi_logo),
|
||||||
contentDescription = "App Logo",
|
contentDescription = "App Logo",
|
||||||
alignment = Alignment.TopCenter
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(0.5f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(8.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(100.dp))
|
Spacer(modifier = Modifier.height(40.dp))
|
||||||
Text(text = "Credentials", style = MaterialTheme.typography.headlineMedium)
|
Text(text = "Credentials", style = MaterialTheme.typography.headlineMedium)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
// User ID TextField
|
OutlinedTextFieldWithError(
|
||||||
OutlinedTextField(
|
|
||||||
value = userId,
|
value = userId,
|
||||||
onValueChange = { userId = it },
|
onValueChange = { userId = it },
|
||||||
label = { Text("User ID") },
|
label = "User ID",
|
||||||
|
isError = isUserIdError,
|
||||||
|
errorMessage = "Please enter a valid User ID",
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Next)
|
leadingIcon = {
|
||||||
)
|
Icon(
|
||||||
|
imageVector = Icons.Default.Person,
|
||||||
Spacer(modifier = Modifier.height(30.dp))
|
contentDescription = "User Icon"
|
||||||
|
)
|
||||||
// Password TextField
|
|
||||||
OutlinedTextField(
|
|
||||||
value = password,
|
|
||||||
onValueChange = { password = it },
|
|
||||||
label = { Text("Password") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
|
||||||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
|
|
||||||
trailingIcon = {
|
|
||||||
val icon = if (passwordVisible) {
|
|
||||||
painterResource(id = R.drawable.baseline_visibility_24)
|
|
||||||
} else {
|
|
||||||
painterResource(id = R.drawable.baseline_visibility_off_24)
|
|
||||||
}
|
|
||||||
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
|
||||||
Icon(painter = icon, contentDescription = "Toggle Password Visibility")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(60.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
OutlinedTextFieldWithError(
|
||||||
|
value = password,
|
||||||
|
onValueChange = { password = it },
|
||||||
|
label = "Password",
|
||||||
|
isError = isPasswordError,
|
||||||
|
errorMessage = "Please enter a valid password",
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Lock,
|
||||||
|
contentDescription = "Password Icon"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailingIcon = {
|
||||||
|
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(
|
||||||
|
id = if (passwordVisible) R.drawable.baseline_visibility_24 else R.drawable.baseline_visibility_off_24
|
||||||
|
),
|
||||||
|
contentDescription = if (passwordVisible) "Hide Password" else "Show Password"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation()
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
// Login Button
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
loginViewModel.login(userId.text, password.text, onLoginSuccess)
|
loginViewModel.login(userId, password, onLoginSuccess)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(50.dp),
|
.height(50.dp),
|
||||||
enabled = !isLoading
|
|
||||||
) {
|
) {
|
||||||
if (isLoading) {
|
Text(
|
||||||
CircularProgressIndicator(
|
text = "Login",
|
||||||
color = Color.White,
|
fontSize = 20.sp
|
||||||
modifier = Modifier.size(24.dp),
|
)
|
||||||
strokeWidth = 2.dp
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = "Login",
|
|
||||||
fontSize = 20.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,35 @@
|
|||||||
package `in`.sminnovations.attendanceapp.screens.login.viewModel
|
package `in`.sminnovations.attendanceapp.screens.login.viewModel
|
||||||
|
|
||||||
// File: LoginViewModel.kt
|
|
||||||
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.google.firebase.firestore.DocumentSnapshot
|
||||||
|
import com.google.firebase.firestore.FirebaseFirestore
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import `in`.sminnovations.attendanceapp.data.dataStore.Employee
|
||||||
import `in`.sminnovations.attendanceapp.screens.login.data.LoginData
|
import `in`.sminnovations.attendanceapp.screens.login.data.LoginData
|
||||||
import `in`.sminnovations.attendanceapp.screens.login.data.LoginDataStore
|
import `in`.sminnovations.attendanceapp.screens.login.data.LoginDataStore
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.tasks.await
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class LoginViewModel @Inject constructor(
|
class LoginViewModel @Inject constructor(
|
||||||
private val loginDataStore: LoginDataStore
|
private val loginDataStore: LoginDataStore, private val firebaseFirestore: FirebaseFirestore
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _isLoading = MutableStateFlow(false)
|
private val _isPasswordError = MutableStateFlow(false)
|
||||||
val isLoading = _isLoading.asStateFlow()
|
val isPasswordError = _isPasswordError.asStateFlow()
|
||||||
|
|
||||||
|
|
||||||
|
private val _isIDError = MutableStateFlow(false)
|
||||||
|
val isIdError = _isIDError.asStateFlow()
|
||||||
|
|
||||||
private val _isLoggedIn = MutableStateFlow(false)
|
private val _isLoggedIn = MutableStateFlow(false)
|
||||||
val isLoggedIn = _isLoggedIn.asStateFlow()
|
val isLoggedIn = _isLoggedIn.asStateFlow()
|
||||||
|
|
||||||
|
|
||||||
private val _loginData = MutableStateFlow(LoginData("", ""))
|
private val _loginData = MutableStateFlow(LoginData("", ""))
|
||||||
val loginData = _loginData.asStateFlow()
|
val loginData = _loginData.asStateFlow()
|
||||||
|
|
||||||
@@ -41,20 +46,35 @@ class LoginViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun login(userId: String, password: String, onLoginSuccess: () -> Unit) {
|
fun login(userId: String, password: String, onLoginSuccess: () -> Unit) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_isLoading.value = true
|
firebaseFirestore.collection("employees").whereEqualTo("Id", userId).get()
|
||||||
delay(1000) // Simulate network call
|
.addOnSuccessListener {
|
||||||
|
if (!it.isEmpty) {
|
||||||
|
_isIDError.value = false
|
||||||
|
for (doc in it) {
|
||||||
|
if (password == doc.getString("Password")) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
loginDataStore.saveLoginData(userId, password)
|
||||||
|
_isLoggedIn.value = true
|
||||||
|
onLoginSuccess()
|
||||||
|
|
||||||
if (userId == "1" && password == "2") {
|
}
|
||||||
loginDataStore.saveLoginData(userId, password)
|
} else {
|
||||||
_isLoggedIn.value = true
|
_isPasswordError.value = true
|
||||||
onLoginSuccess()
|
}
|
||||||
}
|
}
|
||||||
_isLoading.value = false
|
} else {
|
||||||
|
_isIDError.value = true
|
||||||
|
}
|
||||||
|
}.addOnFailureListener {
|
||||||
|
_isIDError.value = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun logout() {
|
fun logout() {
|
||||||
@@ -63,4 +83,31 @@ class LoginViewModel @Inject constructor(
|
|||||||
_isLoggedIn.value = false
|
_isLoggedIn.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun DocumentSnapshot.toEmployee(): Employee {
|
||||||
|
return Employee(
|
||||||
|
androidID = getString("AndroidID") ?: "",
|
||||||
|
dateOfJoining = getString("DateOfJoining") ?: "",
|
||||||
|
dateOfLeaving = getString("DateOfLeaving") ?: "",
|
||||||
|
designation = getString("Designation") ?: "Android Developer",
|
||||||
|
employeeStatus = getBoolean("EmployeeStatus") ?: true,
|
||||||
|
id = getString("Id") ?: "",
|
||||||
|
name = getString("Name") ?: "",
|
||||||
|
password = getString("Password") ?: "",
|
||||||
|
photo = getString("Photo") ?: "",
|
||||||
|
dateOfBirth = getString("dateOfBirth") ?: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun fetchEmployeeData(employeeId: String): Employee? {
|
||||||
|
val db = FirebaseFirestore.getInstance()
|
||||||
|
return try {
|
||||||
|
val document = db.collection("employees").document(employeeId).get().await()
|
||||||
|
document.toEmployee()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user