diff --git a/feature/src/main/java/team/aliens/dms/android/feature/resetpassword/AccountVerificationScreen.kt b/feature/src/main/java/team/aliens/dms/android/feature/resetpassword/AccountVerificationScreen.kt
index e8b1a6b5b..75511a084 100644
--- a/feature/src/main/java/team/aliens/dms/android/feature/resetpassword/AccountVerificationScreen.kt
+++ b/feature/src/main/java/team/aliens/dms/android/feature/resetpassword/AccountVerificationScreen.kt
@@ -9,6 +9,9 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -16,7 +19,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
@@ -24,9 +26,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.input.ImeAction
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.ramcosta.composedestinations.annotation.Destination
-import kotlinx.coroutines.delay
import team.aliens.dms.android.core.designsystem.ContainedButton
import team.aliens.dms.android.core.designsystem.DmsTheme
import team.aliens.dms.android.core.designsystem.DmsTopAppBar
@@ -60,15 +62,6 @@ fun AccountVerificationScreen(
val context = LocalContext.current
val (idChecked, onChangeIdChecked) = rememberSaveable { mutableStateOf(false) }
- LaunchedEffect(uiState.accountId) {
- if (uiState.accountId.isNotEmpty()) {
- delay(300L)
- viewModel.postIntent(ResetPasswordIntent.CheckAccountId)
- }
- }
-
- val isAccountIdError by rememberSaveable(uiState.accountId) { mutableStateOf(false) }
-
viewModel.sideEffectFlow.collectInLaunchedEffectWithLifecycle { sideEffect ->
when (sideEffect) {
ResetPasswordSideEffect.AccountIdExists -> {
@@ -79,14 +72,17 @@ fun AccountVerificationScreen(
message = context.getString(R.string.reset_password_account_verification_account_id_does_not_exist),
)
- ResetPasswordSideEffect.EmailVerificationTooManyRequest -> toast.showErrorToast(
- message = context.getString(R.string.reset_password_account_verification_error_too_many_request),
+ ResetPasswordSideEffect.EmailVerificationUserNotFound -> toast.showErrorToast(
+ message = context.getString(R.string.reset_password_account_verification_error_user_not_found),
+ )
+
+ ResetPasswordSideEffect.InvalidEmailFormat -> toast.showErrorToast(
+ message = context.getString(R.string.reset_password_account_verification_error_invalid_email_format)
)
ResetPasswordSideEffect.SendEmailVerificationCodeSuccess -> navigator.openResetPasswordEnterEmailVerificationCode()
- else -> { /* explicit blank */
- }
+ else -> { /* explicit blank */ }
}
}
@@ -112,7 +108,8 @@ fun AccountVerificationScreen(
modifier = Modifier
.fillMaxSize()
.padding(padValues)
- .imePadding(),
+ .imePadding()
+ .verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(DefaultVerticalSpace),
) {
Banner(
@@ -134,12 +131,13 @@ fun AccountVerificationScreen(
Text(text = stringResource(id = R.string.reset_password_account_verification_enter_account_id))
},
onValueChange = { viewModel.postIntent(ResetPasswordIntent.UpdateAccountId(value = it)) },
- supportingText = if (isAccountIdError) {
+ supportingText = if (uiState.isAccountIdError) {
{ Text(text = stringResource(id = R.string.reset_password_account_verification_enter_account_id_invalid_format)) }
} else {
null
},
- isError = isAccountIdError,
+ isError = uiState.isAccountIdError,
+ readOnly = idChecked,
)
AnimatedVisibility(
modifier = Modifier.fillMaxWidth(),
@@ -167,6 +165,9 @@ fun AccountVerificationScreen(
onValueChange = {
viewModel.postIntent(ResetPasswordIntent.UpdateStudentName(value = it))
},
+ keyboardOptions = KeyboardOptions(
+ imeAction = ImeAction.Next
+ ),
)
TextField(
modifier = Modifier
@@ -189,6 +190,7 @@ fun AccountVerificationScreen(
.horizontalPadding()
.bottomPadding(),
onClick = {
+
viewModel.postIntent(
ResetPasswordIntent.SendEmailVerificationCode(
uiState.email,
diff --git a/feature/src/main/java/team/aliens/dms/android/feature/resetpassword/ResetPasswordViewModel.kt b/feature/src/main/java/team/aliens/dms/android/feature/resetpassword/ResetPasswordViewModel.kt
index cb4dca42b..3ca99aa47 100644
--- a/feature/src/main/java/team/aliens/dms/android/feature/resetpassword/ResetPasswordViewModel.kt
+++ b/feature/src/main/java/team/aliens/dms/android/feature/resetpassword/ResetPasswordViewModel.kt
@@ -3,6 +3,10 @@ package team.aliens.dms.android.feature.resetpassword
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import team.aliens.dms.android.core.ui.mvi.BaseMviViewModel
import team.aliens.dms.android.core.ui.mvi.Intent
@@ -11,10 +15,13 @@ import team.aliens.dms.android.core.ui.mvi.UiState
import team.aliens.dms.android.data.auth.model.EmailVerificationType
import team.aliens.dms.android.data.auth.repository.AuthRepository
import team.aliens.dms.android.data.student.repository.StudentRepository
+import team.aliens.dms.android.shared.validator.checkIfEmailValid
import team.aliens.dms.android.shared.validator.checkIfPasswordValid
import java.util.UUID
import javax.inject.Inject
+const val SEARCH_DEBOUNCE_MILLIS = 1000L
+
@HiltViewModel
class ResetPasswordViewModel @Inject constructor(
private val studentRepository: StudentRepository,
@@ -27,6 +34,10 @@ class ResetPasswordViewModel @Inject constructor(
검사에서 가능이 뜨게 된다면 "이메일 인증번호 보내기 APi"를 사용해서 사용자 이메일에 이메일을 발송합니다.
그리고 이메일 인증번호 확인 Api를 사용하여 인증을 완료하고 Students의 비밀번호 재설정 Api를 사용하여 재설정합니다.*/
+ init {
+ debounceName()
+ }
+
override fun processIntent(intent: ResetPasswordIntent) {
when (intent) {
ResetPasswordIntent.SetPassword -> resetPassword()
@@ -43,6 +54,17 @@ class ResetPasswordViewModel @Inject constructor(
}
}
+ @OptIn(FlowPreview::class)
+ private fun debounceName() {
+ viewModelScope.launch {
+ stateFlow.map { it.accountId }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS).collect {
+ if (it.isNotBlank()) {
+ checkIdExists()
+ }
+ }
+ }
+ }
+
private fun resetPassword() = viewModelScope.launch(Dispatchers.IO) {
val capturedState = stateFlow.value
if (capturedState.newPassword != capturedState.newPasswordRepeat) {
@@ -82,30 +104,40 @@ class ResetPasswordViewModel @Inject constructor(
reduce(
newState = stateFlow.value.copy(
hashedEmail = it,
+ isAccountIdError = false,
),
)
postSideEffect(ResetPasswordSideEffect.AccountIdExists)
}.onFailure {
+ reduce(
+ newState = stateFlow.value.copy(
+ isAccountIdError = true,
+ ),
+ )
postSideEffect(ResetPasswordSideEffect.AccountIdNotExists)
}
}
private fun sendEmailVerificationCode(email: String) =
- runCatching {
- viewModelScope.launch(Dispatchers.IO) {
+ viewModelScope.launch(Dispatchers.IO) {
+ if (!checkIfEmailValid(email)) {
+ postSideEffect(ResetPasswordSideEffect.InvalidEmailFormat)
+ return@launch
+ }
+ runCatching {
authRepository.sendEmailVerificationCode(
email = email,
type = EmailVerificationType.PASSWORD,
)
+ }.onSuccess {
+ postSideEffect(ResetPasswordSideEffect.SendEmailVerificationCodeSuccess)
+ }.onFailure {
+ postSideEffect(ResetPasswordSideEffect.EmailVerificationUserNotFound)
}
- }.onSuccess {
- postSideEffect(ResetPasswordSideEffect.SendEmailVerificationCodeSuccess)
- }.onFailure {
- postSideEffect(ResetPasswordSideEffect.EmailVerificationTooManyRequest)
}
private fun updateEmailVerificationCode(value: String) = run {
- if (value.length > ResetPasswordViewModel.EMAIL_VERIFICATION_CODE_LENGTH) {
+ if (value.length > EMAIL_VERIFICATION_CODE_LENGTH) {
return@run false
}
reduce(newState = stateFlow.value.copy(emailVerificationCode = value))
@@ -182,6 +214,7 @@ data class ResetPasswordUiState(
val newPasswordRepeat: String,
val hashedEmail: String,
val sessionId: UUID,
+ val isAccountIdError: Boolean,
) : UiState() {
companion object {
fun initial() = ResetPasswordUiState(
@@ -193,6 +226,7 @@ data class ResetPasswordUiState(
newPasswordRepeat = "",
hashedEmail = "",
sessionId = UUID.randomUUID(),
+ isAccountIdError = false
)
}
}
@@ -223,5 +257,6 @@ sealed class ResetPasswordSideEffect : SideEffect() {
data object EmailVerificationCodeIncorrect : ResetPasswordSideEffect()
data object EmailVerificationSessionReset : ResetPasswordSideEffect()
data object EmailVerificationSessionResetFailed : ResetPasswordSideEffect()
- data object EmailVerificationTooManyRequest : ResetPasswordSideEffect()
+ data object EmailVerificationUserNotFound : ResetPasswordSideEffect()
+ data object InvalidEmailFormat : ResetPasswordSideEffect()
}
diff --git a/feature/src/main/res/values/strings.xml b/feature/src/main/res/values/strings.xml
index d2a8d28df..19e00761c 100644
--- a/feature/src/main/res/values/strings.xml
+++ b/feature/src/main/res/values/strings.xml
@@ -377,6 +377,8 @@
아이디와 일치하는 이메일입니다
아이디 형식이 일치하지 않습니다.
요청이 너무 많습니다 잠시 후 다시 시도해주세요
+ 이름이나 이메일이 일치하지 않습니다.
+ 이메일 형식에 맞춰 작성해주세요
이름 입력
이메일 입력
비밀번호가 변경되었습니다.
diff --git a/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/EmailValidator.kt b/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/EmailValidator.kt
index adc8a8660..1a4f3ad88 100644
--- a/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/EmailValidator.kt
+++ b/shared/validator/src/main/java/team/aliens/dms/android/shared/validator/EmailValidator.kt
@@ -2,7 +2,7 @@ package team.aliens.dms.android.shared.validator
object EmailValidator : Validator() {
override val regex =
- Regex("^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}\$")
+ Regex("^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*\\.[a-zA-Z]{2,3}\$")
override fun validate(value: String): Boolean = value.matches(regex)
}