diff --git a/app/src/main/java/org/sopt/and/component/CustomTextField.kt b/app/src/main/java/org/sopt/and/component/CustomTextField.kt deleted file mode 100644 index 477f2e6..0000000 --- a/app/src/main/java/org/sopt/and/component/CustomTextField.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.sopt.and.component - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.material3.TextFieldDefaults -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp - -@Composable -fun CustomTextField( - value: String, - onValueChange: (String) -> Unit, - placeholder: String, - passwordVisible: Boolean = true, - padding: PaddingValues = PaddingValues(0.dp), -) { - TextField( - modifier = Modifier - .fillMaxWidth() - .padding(padding), - value = value, - onValueChange = onValueChange, - placeholder = { Text(placeholder, color = Color(0xFFA5A5A5), fontSize = 14.sp) }, - shape = RoundedCornerShape(10.dp), - visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), // 암호화 적용 - colors = TextFieldDefaults.colors( - focusedContainerColor = Color(0xFF2F2F2F), - unfocusedContainerColor = Color(0xFF2F2F2F), - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - focusedTextColor = Color.White, - unfocusedTextColor = Color.White, - ), - maxLines = 1, - ) -} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/and/feature/login/LoginScreen.kt b/app/src/main/java/org/sopt/and/feature/login/LoginScreen.kt index 23edc55..1566a9e 100644 --- a/app/src/main/java/org/sopt/and/feature/login/LoginScreen.kt +++ b/app/src/main/java/org/sopt/and/feature/login/LoginScreen.kt @@ -27,9 +27,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState 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.graphics.Color @@ -42,9 +40,10 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import org.sopt.and.R -import org.sopt.and.component.CustomTextField import org.sopt.and.component.DescriptionText import org.sopt.and.component.DividerWithText +import org.sopt.and.component.textfield.CustomPwTextField +import org.sopt.and.component.textfield.CustomEmailTextField import org.sopt.and.feature.model.UserInfo import org.sopt.and.ui.theme.ANDANDROIDTheme @@ -55,7 +54,6 @@ fun LoginScreen(navController: NavController) { val loginEmail by viewModel.email.collectAsState() val loginPassword by viewModel.password.collectAsState() val isLoginSuccessful by viewModel.isLoginSuccessful.collectAsState() - var passwordVisible by remember { mutableStateOf(false) } val context = LocalContext.current val snackbarHostState = remember { SnackbarHostState() } @@ -69,31 +67,19 @@ fun LoginScreen(navController: NavController) { ) { LoginTopBar() Spacer(modifier = Modifier.padding(top = 30.dp)) - CustomTextField( + CustomEmailTextField( value = loginEmail, onValueChange = { viewModel.updateEmail(it) }, placeholder = stringResource(R.string.login_email_id) ) Spacer(modifier = Modifier.padding(top = 10.dp)) - Box( - modifier = Modifier.fillMaxWidth(), - contentAlignment = Alignment.CenterEnd - ) { - CustomTextField( - value = loginPassword, - onValueChange = { viewModel.updatePassword(it) }, - placeholder = stringResource(R.string.login_setting_password), - passwordVisible = passwordVisible, - padding = PaddingValues(vertical = 10.dp) - ) - Text( - text = if (passwordVisible) "hide" else "show", - color = Color.White, - modifier = Modifier - .padding(end = 10.dp) - .clickable { passwordVisible = !passwordVisible } - ) - } + + CustomPwTextField( + value = loginPassword, + onValueChange = { viewModel.updatePassword(it) }, + placeholder = stringResource(R.string.login_setting_password), + modifier = Modifier.padding(vertical = 10.dp) + ) Spacer(modifier = Modifier.padding(top = 30.dp)) NavigateToMain { @@ -101,7 +87,7 @@ fun LoginScreen(navController: NavController) { } LaunchedEffect(isLoginSuccessful) { - isLoginSuccessful?.let { + isLoginSuccessful.let { if (it) { snackbarHostState.showSnackbar(context.getString(R.string.login_success)) navController.currentBackStackEntry?.arguments?.putParcelable( diff --git a/app/src/main/java/org/sopt/and/feature/signup/SignUpScreen.kt b/app/src/main/java/org/sopt/and/feature/signup/SignUpScreen.kt index 0390af7..4a2abc5 100644 --- a/app/src/main/java/org/sopt/and/feature/signup/SignUpScreen.kt +++ b/app/src/main/java/org/sopt/and/feature/signup/SignUpScreen.kt @@ -2,7 +2,6 @@ package org.sopt.and.feature.signup import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -17,9 +16,6 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState 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.graphics.Color @@ -32,9 +28,10 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import org.sopt.and.R -import org.sopt.and.component.CustomTextField +import org.sopt.and.component.textfield.CustomEmailTextField import org.sopt.and.component.DescriptionText import org.sopt.and.component.DividerWithText +import org.sopt.and.component.textfield.CustomPwTextField import org.sopt.and.core.showToast import org.sopt.and.feature.model.UserInfo import org.sopt.and.ui.theme.ANDANDROIDTheme @@ -46,10 +43,10 @@ fun SignUpScreen(navController: NavController) { val signUpEmail by viewModel.email.collectAsState() val signUpPassword by viewModel.password.collectAsState() - var passwordVisible by remember { mutableStateOf(false) } val isEmailValid by viewModel.isEmailValid.collectAsState() val isPasswordValid by viewModel.isPasswordValid.collectAsState() + val context = LocalContext.current Column( @@ -81,33 +78,23 @@ fun SignUpScreen(navController: NavController) { ) Spacer(modifier = Modifier.padding(top = 20.dp)) - CustomTextField( + CustomEmailTextField( value = signUpEmail, onValueChange = { viewModel.updateEmail(it) }, - placeholder = "wavve@example.com" + placeholder = "wavve@example.com", + isError = !isEmailValid, ) DescriptionText(stringResource(R.string.signup_id_description)) Spacer(modifier = Modifier.padding(top = 20.dp)) - Box( - modifier = Modifier.fillMaxWidth(), - contentAlignment = Alignment.CenterEnd - ) { - CustomTextField( - value = signUpPassword, - onValueChange = { viewModel.updatePassword(it) }, - placeholder = stringResource(R.string.login_setting_password), - passwordVisible = passwordVisible - ) - Text( - text = if (passwordVisible) "hide" else "show", - color = Color.White, - modifier = Modifier - .padding(end = 10.dp) - .clickable { passwordVisible = !passwordVisible } - ) - } + CustomPwTextField( + value = signUpPassword, + onValueChange = { viewModel.updatePassword(it) }, + placeholder = stringResource(R.string.login_setting_password), + isError = !isPasswordValid, + modifier = Modifier.padding(vertical = 10.dp) + ) DescriptionText(stringResource(R.string.signup_password_description)) Spacer(modifier = Modifier.padding(top = 30.dp)) diff --git a/app/src/main/java/org/sopt/and/feature/signup/SignUpViewModel.kt b/app/src/main/java/org/sopt/and/feature/signup/SignUpViewModel.kt index a026ae9..d15816c 100644 --- a/app/src/main/java/org/sopt/and/feature/signup/SignUpViewModel.kt +++ b/app/src/main/java/org/sopt/and/feature/signup/SignUpViewModel.kt @@ -12,27 +12,28 @@ class SignUpViewModel : ViewModel() { private val _password = MutableStateFlow("") val password: StateFlow = _password - private val _isEmailValid = MutableStateFlow(false) + private val _isEmailValid = MutableStateFlow(true) val isEmailValid: StateFlow = _isEmailValid - private val _isPasswordValid = MutableStateFlow(false) + private val _isPasswordValid = MutableStateFlow(true) val isPasswordValid: StateFlow = _isPasswordValid + fun updateEmail(newEmail: String) { _email.value = newEmail - _isEmailValid.value = isValidEmail(newEmail) + _isEmailValid.value = newEmail.isBlank() || isValidEmail(newEmail) } fun updatePassword(newPassword: String) { _password.value = newPassword - _isPasswordValid.value = isValidPassword(newPassword) + _isPasswordValid.value = newPassword.isBlank() || isValidPassword(newPassword) } fun isValidEmail(email: String): Boolean { return Patterns.EMAIL_ADDRESS.matcher(email).matches() } - fun isValidPassword(password: String): Boolean { + private fun isValidPassword(password: String): Boolean { val regex = Regex("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@\$!%*?&])[A-Za-z\\d@\$!%*?&]{8,20}$") return password.matches(regex)