diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0be0dbf0..e3dda9bf 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation(project(":feature:my-page")) implementation(project(":feature:post")) implementation(project(":feature:club")) + implementation(project(":feature:email")) implementation(libs.junit) androidTestImplementation(libs.androidx.test.ext) diff --git a/app/src/main/java/com/msg/bitgoeul_android/navigation/BitgoeulNavHost.kt b/app/src/main/java/com/msg/bitgoeul_android/navigation/BitgoeulNavHost.kt index 541ef069..a472a383 100644 --- a/app/src/main/java/com/msg/bitgoeul_android/navigation/BitgoeulNavHost.kt +++ b/app/src/main/java/com/msg/bitgoeul_android/navigation/BitgoeulNavHost.kt @@ -3,6 +3,14 @@ package com.msg.bitgoeul_android.navigation import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.compose.NavHost +import com.bitgoeul.email.navigation.emailSendInformScreen +import com.bitgoeul.email.navigation.inputEmailScreen +import com.bitgoeul.email.navigation.inputNewPasswordScreen +import com.bitgoeul.email.navigation.navigateToEmailSendInform +import com.bitgoeul.email.navigation.navigateToInputEmail +import com.bitgoeul.email.navigation.navigateToInputNewPassword +import com.bitgoeul.email.navigation.navigateToPasswordChangeSuccess +import com.bitgoeul.email.navigation.passwordChangeSuccessScreen import com.bitgoeul.login.navigation.loginRoute import com.bitgoeul.login.navigation.loginScreen import com.bitgoeul.login.navigation.navigateToLogin @@ -18,7 +26,6 @@ import com.msg.lecture.navigation.lectureDetailScreen import com.msg.lecture.navigation.lectureDetailSettingScreen import com.msg.lecture.navigation.lectureListScreen import com.msg.lecture.navigation.lectureOpenScreen -import com.msg.lecture.navigation.navigateToLecture import com.msg.lecture.navigation.navigateToLectureDetail import com.msg.lecture.navigation.navigateToLectureDetailSetting import com.msg.lecture.navigation.navigateToLectureOpen @@ -52,7 +59,23 @@ fun BitgoeulNavHost( modifier = modifier ) { loginScreen( - onSignUpClick = navController::navigateToSignUp + onSignUpClick = navController::navigateToSignUp, + onFindPasswordClick = navController::navigateToInputEmail + ) + inputEmailScreen( + onBackClicked = navController::navigateToLogin, + onNextClicked = navController::navigateToEmailSendInform + ) + emailSendInformScreen( + onBackClicked = navController::navigateToInputEmail, + onMoveNewPasswordClick = navController::navigateToInputNewPassword + ) + inputNewPasswordScreen( + onBackClicked = navController::navigateToInputEmail, + onNextClicked = navController::navigateToPasswordChangeSuccess + ) + passwordChangeSuccessScreen( + onBackClicked = navController::navigateToLogin ) signUpScreen( onBackClick = navController::popBackStack diff --git a/core/network/src/main/java/com/msg/network/di/NetworkModule.kt b/core/network/src/main/java/com/msg/network/di/NetworkModule.kt index ebd6f1fa..09d8c75c 100644 --- a/core/network/src/main/java/com/msg/network/di/NetworkModule.kt +++ b/core/network/src/main/java/com/msg/network/di/NetworkModule.kt @@ -7,6 +7,7 @@ import com.msg.network.api.AdminAPI import com.msg.network.api.AuthAPI import com.msg.network.api.CertificationAPI import com.msg.network.api.ClubAPI +import com.msg.network.api.EmailAPI import com.msg.network.api.FaqAPI import com.msg.network.api.LectureAPI import com.msg.network.api.PostAPI @@ -20,6 +21,7 @@ import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.create import java.util.concurrent.TimeUnit import javax.inject.Singleton @@ -98,4 +100,8 @@ object NetworkModule { @Provides fun providePostAPI(retrofit: Retrofit): PostAPI = retrofit.create(PostAPI::class.java) + + @Provides + fun provideEmailAPI(retrofit: Retrofit): EmailAPI = + retrofit.create(EmailAPI::class.java) } \ No newline at end of file diff --git a/feature/email/src/main/java/com/bitgoeul/email/EmailSendInformScreen.kt b/feature/email/src/main/java/com/bitgoeul/email/EmailSendInformScreen.kt index ca0b2124..8767db12 100644 --- a/feature/email/src/main/java/com/bitgoeul/email/EmailSendInformScreen.kt +++ b/feature/email/src/main/java/com/bitgoeul/email/EmailSendInformScreen.kt @@ -1,5 +1,8 @@ package com.bitgoeul.email +import android.util.Log +import android.widget.Toast +import androidx.activity.ComponentActivity import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -9,21 +12,94 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import com.bitgoeul.email.util.Event +import com.bitgoeul.email.viewmodel.EmailViewModel import com.msg.design_system.component.icon.GoBackIcon import com.msg.design_system.component.topbar.GoBackTopBar import com.msg.design_system.theme.BitgoeulAndroidTheme import com.msg.design_system.R +import com.msg.model.remote.response.email.GetEmailAuthenticateStatusResponse +import kotlinx.coroutines.launch + +@Composable +fun EmailSendInformRoute( + onBackClicked: () -> Unit, + onMoveNewPasswordClick: () -> Unit, + viewModel: EmailViewModel = hiltViewModel(LocalContext.current as ComponentActivity) +) { + val context = LocalContext.current + val activity = LocalContext.current as ComponentActivity + val coroutineScope = rememberCoroutineScope() + DisposableEffect(activity) { + val observer = object : DefaultLifecycleObserver { + override fun onResume(owner: LifecycleOwner) { + super.onResume(owner) + viewModel.getEmailAuthenticateStatus() + coroutineScope.launch { + getEmailAuthenticateStatus( + viewModel = viewModel, + onSuccess = { response -> + if (response.isAuthentication) { + onMoveNewPasswordClick() + Toast.makeText(context, "이메일 인증에 성공했습니다.", Toast.LENGTH_SHORT).show() + } + if (!response.isAuthentication) { + Toast.makeText(context, "이메일 인증에 실패했습니다.", Toast.LENGTH_SHORT).show() + } + } + ) + } + } + } + activity.lifecycle.addObserver(observer) + onDispose { + activity.lifecycle.removeObserver(observer) + } + } + + EmailSendInformScreen( + onBackClicked = onBackClicked, + emailText = viewModel.email.value + ) +} + + + + +suspend fun getEmailAuthenticateStatus( + viewModel: EmailViewModel, + onSuccess: (data: GetEmailAuthenticateStatusResponse) -> Unit, +) { + viewModel.getEmailAuthenticateStatusResponse.collect { response -> + when (response) { + is Event.Success -> { + onSuccess(response.data!!) + } + + else -> {} + } + } +} @Composable fun EmailSendInformScreen( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + onBackClicked: () -> Unit, + emailText: String, ) { BitgoeulAndroidTheme { color, typography -> Surface { + Log.e("Screen나옴", "test") Column( modifier = modifier .background(color = color.WHITE) @@ -36,14 +112,14 @@ fun EmailSendInformScreen( icon = { GoBackIcon() }, text = stringResource(id = R.string.go_back) ) { - // TODO onBackClicked() 추가하기 + onBackClicked() } Spacer(modifier = modifier.weight(1f)) Text( modifier = modifier.align(Alignment.CenterHorizontally), - text = "s22055@gsm.hs.kr 로", + text = emailText + "으로", style = typography.labelMedium, color = color.G2 ) diff --git a/feature/email/src/main/java/com/bitgoeul/email/InputEmailScreen.kt b/feature/email/src/main/java/com/bitgoeul/email/InputEmailScreen.kt index 3a31e074..f62d84aa 100644 --- a/feature/email/src/main/java/com/bitgoeul/email/InputEmailScreen.kt +++ b/feature/email/src/main/java/com/bitgoeul/email/InputEmailScreen.kt @@ -1,5 +1,6 @@ package com.bitgoeul.email +import androidx.activity.ComponentActivity import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -10,9 +11,14 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.bitgoeul.email.viewmodel.EmailViewModel import com.msg.design_system.component.icon.GoBackIcon import com.msg.design_system.component.topbar.GoBackTopBar import com.msg.design_system.theme.BitgoeulAndroidTheme @@ -21,10 +27,29 @@ import com.msg.design_system.component.button.BitgoeulButton import com.msg.design_system.component.button.ButtonState import com.msg.design_system.component.textfield.DefaultTextField +@Composable +fun InputEmailRoute( + onBackClicked: () -> Unit, + onNextClicked: () -> Unit, + viewModel: EmailViewModel = hiltViewModel(LocalContext.current as ComponentActivity) +) { + InputEmailScreen( + onBackClicked = onBackClicked, + onNextClicked = { email -> + viewModel.email.value = email + viewModel.sendLinkToEmail() + onNextClicked() + } + ) +} @Composable fun InputEmailScreen( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + onBackClicked: () -> Unit, + onNextClicked: (String) -> Unit, ) { + val email = remember { mutableStateOf("") } + BitgoeulAndroidTheme { color, typography -> Surface { Column( @@ -39,7 +64,7 @@ fun InputEmailScreen( icon = { GoBackIcon() }, text = stringResource(id = R.string.go_back) ) { - // TODO onBackClicked() 추가하기 + onBackClicked() } Spacer(modifier = modifier.height(16.dp)) @@ -60,7 +85,9 @@ fun InputEmailScreen( DefaultTextField( modifier = modifier.fillMaxWidth(), - onValueChange = { }, + onValueChange = { inputEmail -> + email.value = inputEmail + }, errorText = "", isDisabled = false, isError = false, @@ -78,8 +105,10 @@ fun InputEmailScreen( .padding(bottom = 14.dp) .fillMaxWidth() .height(52.dp), - state = ButtonState.Disable, - onClick = {} + state = if(email.value.isNotEmpty()) ButtonState.Enable else ButtonState.Disable, + onClick = { + onNextClicked(email.value) + } ) } } diff --git a/feature/email/src/main/java/com/bitgoeul/email/InputNewPasswordScreen.kt b/feature/email/src/main/java/com/bitgoeul/email/InputNewPasswordScreen.kt index 4d211cf4..7eabbcb8 100644 --- a/feature/email/src/main/java/com/bitgoeul/email/InputNewPasswordScreen.kt +++ b/feature/email/src/main/java/com/bitgoeul/email/InputNewPasswordScreen.kt @@ -1,5 +1,7 @@ package com.bitgoeul.email +import android.widget.Toast +import androidx.activity.ComponentActivity import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -10,9 +12,14 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.bitgoeul.email.viewmodel.EmailViewModel import com.msg.design_system.R import com.msg.design_system.component.button.BitgoeulButton import com.msg.design_system.component.button.ButtonState @@ -21,10 +28,33 @@ import com.msg.design_system.component.textfield.DefaultTextField import com.msg.design_system.component.topbar.GoBackTopBar import com.msg.design_system.theme.BitgoeulAndroidTheme +@Composable +fun InputNewPasswordRoute( + onBackClicked: () -> Unit, + onNextClicked: () -> Unit, + viewModel: EmailViewModel = hiltViewModel(LocalContext.current as ComponentActivity) +) { + InputNewPasswordScreen( + onBackClicked = onBackClicked, + onNextClicked = { newPassword -> + viewModel.newPassword.value = newPassword + viewModel.findPassword() + onNextClicked() + }, + ) +} + @Composable fun InputNewPasswordScreen( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + onBackClicked: () -> Unit, + onNextClicked: (String) -> Unit, ) { + val passwordPattern = Regex("^(?=.*[A-Za-z0-9])[A-Za-z0-9!@#$%^&*]{8,24}$") + val firstInputPassword = remember { mutableStateOf("") } + val secondInputPassword = remember { mutableStateOf("") } + val context = LocalContext.current + BitgoeulAndroidTheme { color, typography -> Surface { Column( @@ -39,7 +69,7 @@ fun InputNewPasswordScreen( icon = { GoBackIcon() }, text = stringResource(id = R.string.go_back) ) { - // TODO onBackClicked() 추가하기 + onBackClicked() } Spacer(modifier = modifier.height(16.dp)) @@ -61,7 +91,9 @@ fun InputNewPasswordScreen( DefaultTextField( modifier = modifier.fillMaxWidth(), - onValueChange = { }, + onValueChange = { inputPassword -> + firstInputPassword.value = inputPassword + }, errorText = "", isDisabled = false, isError = false, @@ -76,10 +108,12 @@ fun InputNewPasswordScreen( DefaultTextField( modifier = modifier.fillMaxWidth(), - onValueChange = { }, - errorText = "", + onValueChange = { inputPassword -> + secondInputPassword.value = inputPassword + }, + errorText = "비밀번호가 일치하지 않습니다.", isDisabled = false, - isError = false, + isError = firstInputPassword.value != secondInputPassword.value, isLinked = false, isReverseTrailingIcon = false, onClickButton = {}, @@ -95,8 +129,14 @@ fun InputNewPasswordScreen( .padding(bottom = 14.dp) .fillMaxWidth() .height(52.dp), - state = ButtonState.Disable, - onClick = {} + state = if(firstInputPassword.value.isNotEmpty() && secondInputPassword.value.isNotEmpty()) ButtonState.Enable else ButtonState.Disable, + onClick = { + if (passwordPattern.matches(firstInputPassword.value)) { + onNextClicked(secondInputPassword.value) + } else { + Toast.makeText(context, "비밀번호는 8~24자 영문, 숫자, 특수문자 1개 이상이어야 합니다.", Toast.LENGTH_SHORT).show() + } + } ) } } diff --git a/feature/email/src/main/java/com/bitgoeul/email/PasswordChangeSuccessScreen.kt b/feature/email/src/main/java/com/bitgoeul/email/PasswordChangeSuccessScreen.kt index de4410bc..f16bd65e 100644 --- a/feature/email/src/main/java/com/bitgoeul/email/PasswordChangeSuccessScreen.kt +++ b/feature/email/src/main/java/com/bitgoeul/email/PasswordChangeSuccessScreen.kt @@ -19,9 +19,18 @@ import com.msg.design_system.component.button.BitgoeulButton import com.msg.design_system.component.button.ButtonState import com.msg.design_system.theme.BitgoeulAndroidTheme +@Composable +fun PasswordChangeSuccessRoute( + onBackClicked: () -> Unit +) { + PasswordChangeSuccessScreen( + onBackClicked = onBackClicked + ) +} @Composable fun PasswordChangeSuccessScreen( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + onBackClicked: () -> Unit ) { BitgoeulAndroidTheme { color, typography -> Surface { @@ -65,7 +74,9 @@ fun PasswordChangeSuccessScreen( .fillMaxWidth() .height(52.dp), state = ButtonState.Enable, - onClick = {} + onClick = { + onBackClicked() + } ) } } diff --git a/feature/email/src/main/java/com/bitgoeul/email/navigation/EmailNavigation.kt b/feature/email/src/main/java/com/bitgoeul/email/navigation/EmailNavigation.kt new file mode 100644 index 00000000..aab1f5d4 --- /dev/null +++ b/feature/email/src/main/java/com/bitgoeul/email/navigation/EmailNavigation.kt @@ -0,0 +1,80 @@ +package com.bitgoeul.email.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import com.bitgoeul.email.EmailSendInformRoute +import com.bitgoeul.email.InputEmailRoute +import com.bitgoeul.email.InputNewPasswordRoute +import com.bitgoeul.email.PasswordChangeSuccessRoute + +const val inputEmailRoute = "input_email_route" + +const val emailSendInformRoute = "email_send_inform_route" + +const val inputNewPasswordRoute = "input_new_password_route" + +const val passwordChangeSuccessRoute = "password_change_success_route" + +fun NavController.navigateToInputEmail(navOptions: NavOptions? = null) { + this.navigate(inputEmailRoute, navOptions) +} + +fun NavController.navigateToEmailSendInform(navOptions: NavOptions? = null) { + this.navigate(emailSendInformRoute, navOptions) +} + +fun NavController.navigateToInputNewPassword(navOptions: NavOptions? = null) { + this.navigate(inputNewPasswordRoute, navOptions) +} + +fun NavController.navigateToPasswordChangeSuccess(navOptions: NavOptions? = null) { + this.navigate(passwordChangeSuccessRoute, navOptions) +} + +fun NavGraphBuilder.inputEmailScreen( + onBackClicked: () -> Unit, + onNextClicked: () -> Unit, +) { + composable(route = inputEmailRoute) { + InputEmailRoute( + onBackClicked = onBackClicked, + onNextClicked = onNextClicked, + ) + } +} + +fun NavGraphBuilder.emailSendInformScreen( + onBackClicked: () -> Unit, + onMoveNewPasswordClick: () -> Unit, +) { + composable(route = emailSendInformRoute) { + EmailSendInformRoute( + onBackClicked = onBackClicked, + onMoveNewPasswordClick = onMoveNewPasswordClick, + ) + } +} + +fun NavGraphBuilder.inputNewPasswordScreen( + onBackClicked: () -> Unit, + onNextClicked: () -> Unit +) { + composable(route = inputNewPasswordRoute) { + InputNewPasswordRoute( + onBackClicked = onBackClicked, + onNextClicked = onNextClicked + ) + } +} + +fun NavGraphBuilder.passwordChangeSuccessScreen( + onBackClicked: () -> Unit +) { + composable(route = passwordChangeSuccessRoute) { + PasswordChangeSuccessRoute( + onBackClicked = onBackClicked + ) + } +} \ No newline at end of file diff --git a/feature/email/src/main/java/com/bitgoeul/email/util/Event.kt b/feature/email/src/main/java/com/bitgoeul/email/util/Event.kt new file mode 100644 index 00000000..0ca0f460 --- /dev/null +++ b/feature/email/src/main/java/com/bitgoeul/email/util/Event.kt @@ -0,0 +1,59 @@ +package com.bitgoeul.email.util + +sealed class Event( + val data: T? = null, +) { + + object Loading : Event() + + /** + * 성공 + */ + class Success(data: T? = null) : Event(data = data) + + /** + * 400번 요청이 올바르지 않은 경우 + */ + object BadRequest : Event() + + /** + * 401번 비인증 요청 + */ + object Unauthorized : Event() + + /** + * 403번 권한이 없음 + */ + object ForBidden : Event() + + /** + * 404 찾을 수 없는 경우 + */ + object NotFound : Event() + + /** + * 406 맞는 규격이 없는 경우 + */ + object NotAcceptable : Event() + + /** + * 408 요청이 너무 오래 걸리는 경우 + */ + object TimeOut : Event() + + /** + * 409 권한이 없을 때 + */ + object Conflict : Event() + + /** + * 50X 서버에러 + */ + object Server : Event() + + /** + * 예상치 못한 에러 + */ + object UnKnown : Event() + +} \ No newline at end of file diff --git a/feature/email/src/main/java/com/bitgoeul/email/util/errorHandling.kt b/feature/email/src/main/java/com/bitgoeul/email/util/errorHandling.kt new file mode 100644 index 00000000..811329af --- /dev/null +++ b/feature/email/src/main/java/com/bitgoeul/email/util/errorHandling.kt @@ -0,0 +1,68 @@ +package com.bitgoeul.email.util + +import android.util.Log +import com.msg.domain.exception.* + +//추후 리팩토링을 통해 다른 모듈? 패키지로 이동 예정 +suspend fun Throwable.errorHandling( + badRequestAction: suspend () -> Unit = {}, + unauthorizedAction: suspend () -> Unit = {}, + forBiddenAction: suspend () -> Unit = {}, + notFoundAction: suspend () -> Unit = {}, + notAcceptableAction: suspend () -> Unit = {}, + timeOutAction: suspend () -> Unit = {}, + conflictAction: suspend () -> Unit = {}, + serverAction: suspend () -> Unit = {}, + unknownAction: suspend () -> Unit = {}, +): Event = + when (this) { + is BadRequestException -> { + errorLog("BadRequestException", message) + badRequestAction() + Event.BadRequest + } + is UnauthorizedException, is NeedLoginException -> { + errorLog("UnauthorizedException", message) + unauthorizedAction() + Event.Unauthorized + } + is ForBiddenException -> { + errorLog("ForBiddenException", message) + forBiddenAction() + Event.ForBidden + } + is NotFoundException -> { + errorLog("NotFoundException", message) + notFoundAction() + Event.NotFound + } + is NotAcceptableException -> { + errorLog("NotAcceptableException", message) + notAcceptableAction() + Event.NotAcceptable + } + is TimeOutException -> { + errorLog("TimeOutException", message) + timeOutAction() + Event.TimeOut + } + is ConflictException -> { + errorLog("ConflictException", message) + conflictAction() + Event.Conflict + } + is ServerException -> { + errorLog("ServerException", message) + serverAction() + Event.Server + } + else -> { + errorLog("UnKnownException", message) + unknownAction() + Event.UnKnown + } + } + +private fun errorLog(tag: String, msg: String?) { + Log.d("ErrorHandling-$tag", msg ?: "알 수 없는 오류") +} \ No newline at end of file diff --git a/feature/email/src/main/java/com/bitgoeul/email/viewmodel/EmailViewModel.kt b/feature/email/src/main/java/com/bitgoeul/email/viewmodel/EmailViewModel.kt new file mode 100644 index 00000000..eebb6b0d --- /dev/null +++ b/feature/email/src/main/java/com/bitgoeul/email/viewmodel/EmailViewModel.kt @@ -0,0 +1,91 @@ +package com.bitgoeul.email.viewmodel + +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.bitgoeul.email.util.Event +import com.bitgoeul.email.util.errorHandling +import com.msg.domain.activity.ChangePasswordUseCase +import com.msg.domain.auth.FindPasswordUseCase +import com.msg.domain.email.GetEmailAuthenticateStatusUseCase +import com.msg.domain.email.SendLinkToEmailUseCase +import com.msg.model.remote.request.auth.FindPasswordRequest +import com.msg.model.remote.request.email.SendLinkToEmailRequest +import com.msg.model.remote.request.user.ChangePasswordRequest +import com.msg.model.remote.response.email.GetEmailAuthenticateStatusResponse +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class EmailViewModel @Inject constructor( + private val sendLinkToEmailUseCase: SendLinkToEmailUseCase, + private val getEmailAuthenticateStatusUseCase: GetEmailAuthenticateStatusUseCase, + private val findPasswordUseCase: FindPasswordUseCase, +) : ViewModel() { + + private val _sendLinkToEmailResponse = MutableStateFlow>(Event.Loading) + val sendLinkToEmailResponse = _sendLinkToEmailResponse.asStateFlow() + + private val _getEmailAuthenticateStatusResponse = MutableStateFlow>(Event.Loading) + val getEmailAuthenticateStatusResponse = _getEmailAuthenticateStatusResponse.asStateFlow() + + private val _findPasswordResponse = MutableStateFlow>(Event.Loading) + val findPasswordResponse = _findPasswordResponse.asStateFlow() + + var email = mutableStateOf("") + private set + + var newPassword = mutableStateOf("") + private set + + fun getEmailAuthenticateStatus() = viewModelScope.launch { + getEmailAuthenticateStatusUseCase( + email = email.value + ).onSuccess { + it.catch {remoteError -> + _getEmailAuthenticateStatusResponse.value = remoteError.errorHandling() + }.collect { response -> + _getEmailAuthenticateStatusResponse.value = Event.Success(data = response) + } + }.onFailure { error -> + _getEmailAuthenticateStatusResponse.value = error.errorHandling() + } + } + + fun sendLinkToEmail() = viewModelScope.launch { + sendLinkToEmailUseCase( + body = SendLinkToEmailRequest( + email = email.value + ) + ).onSuccess { + it.catch {remoteError -> + _sendLinkToEmailResponse.value = remoteError.errorHandling() + }.collect { + _sendLinkToEmailResponse.value = Event.Success() + } + }.onFailure { error -> + _sendLinkToEmailResponse.value = error.errorHandling() + } + } + + fun findPassword() = viewModelScope.launch { + findPasswordUseCase( + body = FindPasswordRequest( + email = email.value, + newPassword = newPassword.value + ) + ).onSuccess { + it.catch {remoteError -> + _findPasswordResponse.value = remoteError.errorHandling() + }.collect { + _findPasswordResponse.value = Event.Success() + } + }.onFailure { error -> + _findPasswordResponse.value = error.errorHandling() + } + } +} diff --git a/feature/login/src/main/java/com/bitgoeul/login/LoginScreen.kt b/feature/login/src/main/java/com/bitgoeul/login/LoginScreen.kt index 6c39c8e5..7a607e09 100644 --- a/feature/login/src/main/java/com/bitgoeul/login/LoginScreen.kt +++ b/feature/login/src/main/java/com/bitgoeul/login/LoginScreen.kt @@ -40,19 +40,21 @@ import com.msg.model.remote.request.auth.LoginRequest @Composable fun LoginRoute( onSignUpClick: () -> Unit, + onFindPasswordClick: () -> Unit, viewModel: AuthViewModel = hiltViewModel(), ) { val lifecycleOwner = LocalLifecycleOwner.current LoginScreen( onSignUpClick = onSignUpClick, + onFindPasswordClick = onFindPasswordClick, onLoginClick = { viewModel.login(LoginRequest(viewModel.email.value, viewModel.password.value)) observeLoginEvent(lifecycleOwner = lifecycleOwner, viewModel = viewModel) }, setLoginData = { email, password -> viewModel.setLoginData(email = email, password = password) - } + }, ) } @@ -73,9 +75,11 @@ fun observeLoginEvent( Log.e("토큰 저장 실패", "토큰이 유효하지 않습니다.") } } + is Event.UnKnown -> { Log.e("Unknown Event", "Unknown 이벤트가 발생했습니다.") } + else -> { Log.e("토큰 저장 실패", "이벤트 타입이 올바르지 않습니다.") } @@ -88,6 +92,7 @@ fun observeLoginEvent( fun LoginScreen( onSignUpClick: () -> Unit, onLoginClick: () -> Unit = {}, + onFindPasswordClick: () -> Unit, setLoginData: (String, String) -> Unit = { _, _ -> }, ) { LockScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) @@ -166,7 +171,9 @@ fun LoginScreen( onValueChange = { passwordState.value = it }, - onClickLink = {}, + onClickLink = { + onFindPasswordClick() + }, isError = isPasswordErrorStatus.value, isLinked = true, linkText = stringResource(id = R.string.find_password), @@ -214,9 +221,4 @@ fun LoginScreen( } } } -} - -@Composable -fun preview() { - LoginScreen(onSignUpClick = {}) -} +} \ No newline at end of file diff --git a/feature/login/src/main/java/com/bitgoeul/login/navigation/LoginNavigation.kt b/feature/login/src/main/java/com/bitgoeul/login/navigation/LoginNavigation.kt index a2e7c44e..c5234929 100644 --- a/feature/login/src/main/java/com/bitgoeul/login/navigation/LoginNavigation.kt +++ b/feature/login/src/main/java/com/bitgoeul/login/navigation/LoginNavigation.kt @@ -12,10 +12,14 @@ fun NavController.navigateToLogin(navOptions: NavOptions? = null) { this.navigate(loginRoute, navOptions) } -fun NavGraphBuilder.loginScreen(onSignUpClick: () -> Unit) { +fun NavGraphBuilder.loginScreen( + onSignUpClick: () -> Unit, + onFindPasswordClick: () -> Unit, +) { composable(route = loginRoute) { LoginRoute( - onSignUpClick = onSignUpClick + onSignUpClick = onSignUpClick, + onFindPasswordClick = onFindPasswordClick ) } } \ No newline at end of file