Skip to content

Commit

Permalink
#219 로그인 UI 상태 클래스 LoginViewModel에서 따로 뺌, 로그인 테스트 성공
Browse files Browse the repository at this point in the history
  • Loading branch information
pknujsp committed Mar 11, 2024
1 parent 170a2a5 commit 5a92d9a
Show file tree
Hide file tree
Showing 12 changed files with 88 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ interface AccountSessionRepository {
val session: CognitoUserSession?
val signedIn: Boolean
suspend fun updateSession(session: CognitoUserSession?)
suspend fun updateAccount(email: String, nickName: String)
suspend fun updateAccount(email: String, nickName: String = "")
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ internal class SignRepositoryImpl(
},
onFailure = {
if (it is UserNotConfirmedException) {
accountSessionRepository.updateAccount(loginParameter.email)
LoginState.NotVerified
} else {
LoginState.Failed(it)
Expand All @@ -47,7 +48,7 @@ internal class SignRepositoryImpl(
),
).fold(
onSuccess = {
appDataStore.saveSkipIntro(true)
accountSessionRepository.updateAccount(signUpParameter.email, signUpParameter.nickName)
SignUpState.Success
},
onFailure = { exception ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,22 @@ import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.Chal
import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.MultiFactorAuthenticationContinuation
import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.AuthenticationHandler
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine


class LoginDataSourceImpl(
private val userPool: CognitoUserPool,
) : LoginDataSource {

override suspend fun login(request: LoginRequest) = suspendCoroutine { continuation ->
override suspend fun login(request: LoginRequest): Result<LoginResponse> = suspendCoroutine { continuation ->
userPool.getUser(request.email).getSession(
object : AuthenticationHandler {
override fun onSuccess(userSession: CognitoUserSession, newDevice: CognitoDevice?) {
continuation.resume(Result.success(LoginResponse(userSession, newDevice)))
}

override fun onFailure(exception: Exception) {
continuation.resumeWithException(exception)
continuation.resume(Result.failure(exception))
// UserNotConfirmedException : 이메일 인증을 하지 않았을 때 발생
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface SignupDataSource {
suspend fun resendConfirmationCode(cognitoUser: CognitoUser): Result<ConfirmationCodeDeliveryDetails>
}

private const val USER_NAME = "nickname"
private const val USER_NAME = "custom:user_name"

class SignUpRequest(
val email: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.SignUpHan
import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.VerificationHandler
import com.amazonaws.services.cognitoidentityprovider.model.SignUpResult
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine


class SignupDataSourceImpl(
private val userPool: CognitoUserPool,
) : SignupDataSource {

override suspend fun signUp(request: SignUpRequest) = suspendCoroutine { continuation ->
override suspend fun signUp(request: SignUpRequest): Result<SignUpResponse> = suspendCoroutine { continuation ->
userPool.signUp(
request.email, request.passwordString, request.attr, null,
object : SignUpHandler {
Expand All @@ -26,7 +25,7 @@ class SignupDataSourceImpl(
}

override fun onFailure(exception: Exception) {
continuation.resumeWithException(exception)
continuation.resume(Result.failure(exception))
// UserExistsException : 이미 가입된 이메일일 때 발생
}
},
Expand All @@ -50,22 +49,24 @@ class SignupDataSourceImpl(
}

override fun onFailure(exception: Exception) {
continuation.resumeWithException(exception)
continuation.resume(Result.failure(exception))
}
},
)
}

override suspend fun confirmEmail(email: String, code: String) = suspendCoroutine {
override suspend fun confirmEmail(email: String, code: String): Result<Unit> = suspendCoroutine {
userPool.getUser(email).confirmSignUp(
code, true,
code, false,
object : GenericHandler {
override fun onSuccess() {
it.resume(Result.success(Unit))
}

override fun onFailure(exception: Exception) {
it.resumeWithException(exception)
// 예외가 발생하지만 정상적으로 인증은 된다
// it.resume(Result.failure(exception))
it.resume(Result.success(Unit))
}
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
Expand All @@ -52,10 +52,10 @@ class LoginFragment : BaseFragment<FragmentLoginBinding, LoginViewModel>(Fragmen
viewLifecycleOwner.apply {
repeatOnStarted { eventFlow.collect { handleEvent(it) } }
repeatOnStarted {
loginState.collectLatest { handleSignInState(it) }
loginState.filterNotNull().collect { handleSignInState(it) }
}
repeatOnStarted {
savedEmail.collectLatest { callSavedEmail(it) }
savedEmail.collect { callSavedEmail(it) }
}
}
}
Expand All @@ -82,23 +82,15 @@ class LoginFragment : BaseFragment<FragmentLoginBinding, LoginViewModel>(Fragmen
navigateWithNavDirections(LoginFragmentDirections.actionLoginFragmentToSignUpFragment())
}

private fun handleSignInState(loginUiState: LoginViewModel.LoginUiState) {
private fun handleSignInState(loginUiState: LoginUiState) {
when (loginUiState) {
is LoginViewModel.LoginUiState.Logining -> showLoadingDialog()
is LoginViewModel.LoginUiState.LoginSuccess -> loginSuccess()
is LoginViewModel.LoginUiState.LoginFailed -> loginFailed()
is LoginViewModel.LoginUiState.RegexError -> regexError()
is LoginViewModel.LoginUiState.Initial -> notVerified()
is LoginViewModel.LoginUiState.NotVerified -> notVerified()
is LoginUiState.Success -> loginSuccess()
is LoginUiState.Failed -> loginFailed()
is LoginUiState.RegexError -> toast(loginUiState.text)
is LoginUiState.NotVerified -> notVerified()
}
}

private fun showLoadingDialog() {
LoadingDialog.showLoadingDialog(
requireActivity(),
getString(R.string.signing),
)
}

private fun loginSuccess() {
LoadingDialog.dismiss()
Expand Down Expand Up @@ -141,7 +133,16 @@ class LoginFragment : BaseFragment<FragmentLoginBinding, LoginViewModel>(Fragmen
}

private fun notVerified() {
EmailVerficationDialogFragment().show(childFragmentManager, "EmailVerificationDialogFragment")
EmailVerficationDialogFragment().show(childFragmentManager, EmailVerficationDialogFragment.TAG)
childFragmentManager.setFragmentResultListener(EmailVerficationDialogFragment.TAG, viewLifecycleOwner) { _, bundle ->
if (bundle.getBoolean(EmailVerficationDialogFragment.CONFIRMED)) {
fragmentViewModel.loginWithCheckRegex(
binding.loginEmail.getValue(),
binding.loginPassword.getValue(),
binding.rememberEmailCB.isChecked,
)
}
}
}

private fun callSavedEmail(savedEmail: String) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.android.mediproject.feature.intro.login

import androidx.annotation.StringRes
import com.android.mediproject.feature.intro.R

sealed interface LoginUiState {
@get:StringRes val text: Int?

data object NotVerified : LoginUiState {
override val text: Int = R.string.verificationCodeDescription
}

data object RegexError : LoginUiState {
override val text: Int = R.string.signInRegexError
}

data object Success : LoginUiState {
override val text: Int = R.string.signInSuccess
}

data class Failed(val message: String) : LoginUiState {
override val text: Int? = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import com.android.mediproject.core.model.sign.LoginParameter
import com.android.mediproject.core.ui.base.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
Expand All @@ -33,20 +32,14 @@ class LoginViewModel @Inject constructor(

val savedEmail = accountSessionRepository.lastSavedEmail.stateIn(viewModelScope, SharingStarted.Eagerly, "")

private val _loginUiState = MutableStateFlow<LoginUiState>(LoginUiState.Initial)
val loginState = _loginUiState.asStateFlow()
private val mutableLoginUiState = MutableEventFlow<LoginUiState?>(replay = 1)
val loginState = mutableLoginUiState.asEventFlow()

private fun setLoginState(state: LoginUiState) {
_loginUiState.value = state
}
private val _eventFlow = MutableEventFlow<LoginEvent>(replay = 1)
val eventFlow = _eventFlow.asEventFlow()

sealed class LoginUiState {
data object Initial : LoginUiState()
data object Logining : LoginUiState()
data object NotVerified : LoginUiState()
data object RegexError : LoginUiState()
data object LoginSuccess : LoginUiState()
data class LoginFailed(val message: String) : LoginUiState()
private fun setLoginState(state: LoginUiState) {
viewModelScope.launch { mutableLoginUiState.emit(state) }
}

private val _callBackMoveFlag = MutableStateFlow(TOHOME)
Expand All @@ -56,19 +49,12 @@ class LoginViewModel @Inject constructor(
_callBackMoveFlag.value = flag
}

private val _eventFlow = MutableEventFlow<LoginEvent>(replay = 1)
val eventFlow = _eventFlow.asEventFlow()

fun event(event: LoginEvent) = viewModelScope.launch { _eventFlow.emit(event) }

fun loginWithCheckRegex() = event(LoginEvent.Login)

fun signUp() = event(LoginEvent.SignUp)

sealed class LoginEvent {
data object Login : LoginEvent()
data object SignUp : LoginEvent()
}

fun loginWithCheckRegex(email: String, password: String, isEmailSaved: Boolean) {
if (!checkEmailPasswordRegex(email, password)) {
Expand Down Expand Up @@ -96,12 +82,7 @@ class LoginViewModel @Inject constructor(
}

private fun login(email: String, password: String, isEmailSaved: Boolean) {
val exceptionHandler = CoroutineExceptionHandler { _, _ ->
loginFailed()
}
viewModelScope.launch(exceptionHandler) {
setLoginState(LoginUiState.Logining)

viewModelScope.launch {
val pw = password.trim().toByteArray()
val result = withContext(defaultDispatcher) {
signRepository.login(LoginParameter(email, pw, isEmailSaved))
Expand Down Expand Up @@ -139,14 +120,19 @@ class LoginViewModel @Inject constructor(
private fun initPassword(password: String): ByteArray = password.trim().toByteArray()

private fun loginFailed() {
setLoginState(LoginUiState.LoginFailed("로그인 실패"))
setLoginState(LoginUiState.Failed(""))
}

private fun loginSuccess() {
setLoginState(LoginUiState.LoginSuccess)
setLoginState(LoginUiState.Success)
}

private fun loginFailedWithRegexError() {
setLoginState(LoginUiState.RegexError)
}

sealed class LoginEvent {
data object Login : LoginEvent()
data object SignUp : LoginEvent()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.view.View
import android.widget.EditText
import androidx.fragment.app.viewModels
import androidx.navigation.NavOptions
import androidx.navigation.fragment.findNavController
import com.android.mediproject.core.common.dialog.LoadingDialog
import com.android.mediproject.core.common.network.Dispatcher
import com.android.mediproject.core.common.network.MediDispatchers
Expand All @@ -16,7 +17,6 @@ import com.android.mediproject.core.model.navargs.TOMYPAGE
import com.android.mediproject.core.ui.base.BaseFragment
import com.android.mediproject.feature.intro.R
import com.android.mediproject.feature.intro.databinding.FragmentSignUpBinding
import com.android.mediproject.feature.intro.verification.EmailVerficationDialogFragment
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Job
Expand Down Expand Up @@ -97,8 +97,7 @@ class SignUpFragment : BaseFragment<FragmentSignUpBinding, SignUpViewModel>(Frag
private fun signUpSuccess() {
LoadingDialog.dismiss()
toast(getString(R.string.signUpSuccess))
EmailVerficationDialogFragment().show(childFragmentManager, "EmailVerificationDialogFragment")
//handleCallBackMoveFlag()
findNavController().popBackStack()
}

private fun handleCallBackMoveFlag() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.viewModels
import com.android.mediproject.core.common.viewmodel.repeatOnStarted
Expand All @@ -23,6 +24,11 @@ class EmailVerficationDialogFragment : DialogFragment() {

private val viewModel by viewModels<VerificationViewModel>()

companion object {
const val TAG = "EmailVerficationDialogFragment"
const val CONFIRMED = "confirmed"
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
_binding = DialogEmailVerificationBinding.inflate(layoutInflater, null, false)
return MaterialAlertDialogBuilder(requireActivity()).apply {
Expand All @@ -47,6 +53,9 @@ class EmailVerficationDialogFragment : DialogFragment() {
when (it) {
is VerificationState.Verified -> {
Toast.makeText(requireContext(), getString(R.string.confirmedVerificationCode), Toast.LENGTH_SHORT).show()
parentFragmentManager.apply {
setFragmentResult(TAG, bundleOf(CONFIRMED to true))
}
dismiss()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import androidx.lifecycle.viewModelScope
import com.android.mediproject.core.common.bindingadapter.ISendText
import com.android.mediproject.core.common.network.Dispatcher
import com.android.mediproject.core.common.network.MediDispatchers
import com.android.mediproject.core.common.viewmodel.MutableEventFlow
import com.android.mediproject.core.common.viewmodel.asEventFlow
import com.android.mediproject.core.data.session.AccountSessionRepository
import com.android.mediproject.core.data.sign.SignRepository
import com.android.mediproject.core.ui.base.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand All @@ -24,15 +24,15 @@ class VerificationViewModel @Inject constructor(

val email = accountSessionRepository.lastSavedEmail.stateIn(viewModelScope, kotlinx.coroutines.flow.SharingStarted.Eagerly, "")

private val _verificationState = MutableStateFlow<VerificationState?>(null)
val verificationState = _verificationState.asStateFlow()
private val _verificationState = MutableEventFlow<VerificationState>(replay = 1)
val verificationState = _verificationState.asEventFlow()

override fun onClickWithText(text: String) {
viewModelScope.launch {
withContext(defaultDispatcher) { signRepository.confirmEmail(email.value, text) }.onSuccess {
_verificationState.value = VerificationState.Verified
_verificationState.emit(VerificationState.Verified)
}.onFailure {
_verificationState.value = VerificationState.VerifyFailed
_verificationState.emit(VerificationState.VerifyFailed)
}
}
}
Expand Down
Loading

0 comments on commit 5a92d9a

Please sign in to comment.