diff --git a/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteDataSource.kt b/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteDataSource.kt index b36ee84d..16175490 100644 --- a/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteDataSource.kt +++ b/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteDataSource.kt @@ -5,4 +5,5 @@ import com.everymeal.domain.model.auth.EmailAuthToken interface AuthRemoteDataSource { suspend fun postEmail(email: Email): Result + suspend fun verifyToken(emailAuthToken: String, emailAuthValue: String): Result } diff --git a/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteRemoteDataSourceImpl.kt b/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteRemoteDataSourceImpl.kt index d05549ea..3be62dab 100644 --- a/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteRemoteDataSourceImpl.kt +++ b/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteRemoteDataSourceImpl.kt @@ -17,4 +17,8 @@ class AuthRemoteRemoteDataSourceImpl @Inject constructor( override suspend fun postEmail(email: Email): Result = runCatching { authApi.postEmail(email.toEmailRequest()).data.toEmailAuthToken() } + + override suspend fun verifyToken(emailAuthToken: String, emailAuthValue: String) = runCatching { + authApi.verifyToken(emailAuthToken, emailAuthValue).data + } } diff --git a/data/src/main/java/com/everymeal/data/repository/DefaultAuthRepository.kt b/data/src/main/java/com/everymeal/data/repository/DefaultAuthRepository.kt index 3d63309d..ebed66e4 100644 --- a/data/src/main/java/com/everymeal/data/repository/DefaultAuthRepository.kt +++ b/data/src/main/java/com/everymeal/data/repository/DefaultAuthRepository.kt @@ -12,4 +12,11 @@ class DefaultAuthRepository @Inject constructor( override suspend fun postEmail(email: Email): Result { return authRemoteDataSource.postEmail(email) } + + override suspend fun verifyToken( + emailAuthToken: String, + emailAuthValue: String + ): Result { + return authRemoteDataSource.verifyToken(emailAuthToken, emailAuthValue) + } } diff --git a/data/src/main/java/com/everymeal/data/service/auth/AuthApi.kt b/data/src/main/java/com/everymeal/data/service/auth/AuthApi.kt index fc685b30..df35624a 100644 --- a/data/src/main/java/com/everymeal/data/service/auth/AuthApi.kt +++ b/data/src/main/java/com/everymeal/data/service/auth/AuthApi.kt @@ -4,11 +4,19 @@ import com.everymeal.data.model.BaseResponse import com.everymeal.data.model.auth.EmailRequest import com.everymeal.data.model.auth.EmailResponse import retrofit2.http.Body +import retrofit2.http.GET import retrofit2.http.POST +import retrofit2.http.Query interface AuthApi { @POST("/api/v1/users/email") suspend fun postEmail( @Body emailRequest: EmailRequest ): BaseResponse + + @GET("/api/v1/users/email/verify") + suspend fun verifyToken( + @Query("emailAuthToken") emailAuthToken: String, + @Query("emailAuthValue") emailAuthValue: String + ): BaseResponse } diff --git a/domain/src/main/java/com/everymeal/domain/repository/auth/AuthRepository.kt b/domain/src/main/java/com/everymeal/domain/repository/auth/AuthRepository.kt index 08fd3fad..22a84009 100644 --- a/domain/src/main/java/com/everymeal/domain/repository/auth/AuthRepository.kt +++ b/domain/src/main/java/com/everymeal/domain/repository/auth/AuthRepository.kt @@ -5,5 +5,6 @@ import com.everymeal.domain.model.auth.EmailAuthToken interface AuthRepository { suspend fun postEmail(email: Email): Result + suspend fun verifyToken(emailAuthToken: String, emailAuthValue: String): Result } diff --git a/domain/src/main/java/com/everymeal/domain/usecase/auth/VerifyTokenUseCase.kt b/domain/src/main/java/com/everymeal/domain/usecase/auth/VerifyTokenUseCase.kt new file mode 100644 index 00000000..84d3d6b9 --- /dev/null +++ b/domain/src/main/java/com/everymeal/domain/usecase/auth/VerifyTokenUseCase.kt @@ -0,0 +1,15 @@ +package com.everymeal.domain.usecase.auth + +import com.everymeal.domain.repository.auth.AuthRepository +import javax.inject.Inject + +class VerifyTokenUseCase @Inject constructor( + private val authRepository: AuthRepository +) { + suspend operator fun invoke( + emailAuthToken: String, + emailAuthValue: String + ): Result { + return authRepository.verifyToken(emailAuthToken, emailAuthValue) + } +} diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt index 6a86a76f..81cd578d 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt @@ -22,6 +22,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.everymeal.presentation.R import com.everymeal.presentation.components.EveryMealConditionAgreeDialog import com.everymeal.presentation.components.EveryMealConditionAgreeDialogItem +import com.everymeal.presentation.ui.signup.school.email.EmailTokenVerifyScreen import com.everymeal.presentation.ui.signup.school.email.SchoolAuthPostEmailScreen import com.everymeal.presentation.ui.theme.EveryMealTypography @@ -41,11 +42,14 @@ fun SchoolAuthScreen( viewModel.effect.collect { effect -> when (effect) { is SchoolContract.Effect.Error -> { - // TODO Error 처리 + Log.e( + "SchoolAuthScreen", + "code: ${effect.code.toString()} message: ${effect.message}" + ) } is SchoolContract.Effect.SuccessEmailVerification -> { - // TODO 이메일 인증 성공 + onSuccessEmailVerification() } } } @@ -112,7 +116,12 @@ private fun EmailAuthBottomSheet(viewModel: SchoolAuthViewModel) { }, onNextButtonClicked = { if (conditionItems.filter { it.isEssential }.any { it.isAgreed }) { - Log.d("TAG", "EmailAuthBottomSheet: ${conditionItems.filter { it.isEssential }.any { it.isAgreed }}") + Log.d( + "TAG", + "EmailAuthBottomSheet: ${ + conditionItems.filter { it.isEssential }.any { it.isAgreed } + }" + ) viewModel.setEvent(SchoolContract.Event.OnPostEmail) } }, @@ -127,9 +136,22 @@ fun SchoolAuthContent( viewModel: SchoolAuthViewModel, state: SchoolContract.State ) { - SchoolAuthPostEmailScreen( - modifier = modifier, - viewModel = viewModel, - state = state, - ) + when (state.schoolAuthScreenType) { + SchoolAuthScreenType.POST_EMAIL -> { + SchoolAuthPostEmailScreen( + modifier = modifier, + viewModel = viewModel, + state = state, + ) + } + + SchoolAuthScreenType.VERIFY_TOKEN -> { + EmailTokenVerifyScreen( + modifier = modifier, + state = state, + viewModel = viewModel + ) + } + } + } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt index 4831b84d..164d9d5b 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt @@ -4,6 +4,7 @@ import android.util.Log import androidx.lifecycle.viewModelScope import com.everymeal.domain.model.auth.Email import com.everymeal.domain.usecase.auth.PostEmailUseCase +import com.everymeal.domain.usecase.auth.VerifyTokenUseCase import com.everymeal.presentation.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch @@ -12,7 +13,8 @@ import javax.inject.Inject @HiltViewModel class SchoolAuthViewModel @Inject constructor( - private val postEmailUseCase: PostEmailUseCase + private val postEmailUseCase: PostEmailUseCase, + private val verifyTokenUseCase: VerifyTokenUseCase ) : BaseViewModel(SchoolContract.State()) { @@ -33,17 +35,12 @@ class SchoolAuthViewModel @Inject constructor( is SchoolContract.Event.OnTokenTextChanged -> { updateState { - copy( - tokenText = event.token - ) + copy(emailAuthValue = event.authValue) } } is SchoolContract.Event.OnEmailNextButtonClicked -> { - if (viewState.value.isEmailError) { - // TODO 이메일 에러 - sendEffect({ SchoolContract.Effect.Error(400) }) - } else { + if (!viewState.value.isEmailError) { updateState { copy(isShowConditionBottomSheet = true) } @@ -59,10 +56,7 @@ class SchoolAuthViewModel @Inject constructor( } SchoolContract.Event.OnTokenNextButtonClicked -> { - val viewState = viewState.value - if (viewState.emailAuthToken == viewState.tokenText) { - sendEffect({ SchoolContract.Effect.SuccessEmailVerification }) - } + verifyToken() } } } @@ -77,12 +71,36 @@ class SchoolAuthViewModel @Inject constructor( postEmailUseCase(Email(viewState.value.emailText)).onSuccess { Log.d("SchoolAuthViewModel", "postEmail: $it") updateState { - copy(emailAuthToken = it.emailAuthToken) + copy( + emailAuthToken = it.emailAuthToken, + schoolAuthScreenType = SchoolAuthScreenType.VERIFY_TOKEN, + isShowConditionBottomSheet = false + ) + } + }.onFailure { + Log.d("SchoolAuthViewModel", "postEmail: $it") + when (it) { + is HttpException -> sendEffect({ SchoolContract.Effect.Error(code = it.code()) }) + else -> sendEffect({ SchoolContract.Effect.Error(message = it.message) }) } + } + } + } + + private fun verifyToken() { + viewModelScope.launch { + val state = viewState.value + verifyTokenUseCase( + emailAuthToken = state.emailAuthToken, + emailAuthValue = state.emailAuthValue + ).onSuccess { + Log.d("SchoolAuthViewModel", "postEmail: $it") + sendEffect({ SchoolContract.Effect.SuccessEmailVerification }) }.onFailure { Log.d("SchoolAuthViewModel", "postEmail: $it") - if (it is HttpException) { - sendEffect({ SchoolContract.Effect.Error(it.code()) }) + when (it) { + is HttpException -> sendEffect({ SchoolContract.Effect.Error(code = it.code()) }) + else -> sendEffect({ SchoolContract.Effect.Error(message = it.message) }) } } } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt index 0664f817..51ebab9b 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt @@ -11,13 +11,13 @@ class SchoolContract { val isShowConditionBottomSheet: Boolean = false, val isEmailError: Boolean = false, val emailText: String = "", - val tokenText: String = "", + val emailAuthValue: String = "", val emailAuthToken: String = "" ) : ViewState sealed class Event : ViewEvent { data class OnEmailTextChanged(val emailLink: String) : Event() - data class OnTokenTextChanged(val token: String) : Event() + data class OnTokenTextChanged(val authValue: String) : Event() object OnEmailNextButtonClicked : Event() object OnTokenNextButtonClicked : Event() object OnPostEmail : Event() @@ -25,7 +25,11 @@ class SchoolContract { } sealed class Effect : ViewSideEffect { - data class Error(val code: Int) : Effect() + data class Error( + val code: Int? = null, + val message: String? = null + ) : Effect() + object SuccessEmailVerification : Effect() } } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/email/EmailTokenVerifyScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/email/EmailTokenVerifyScreen.kt new file mode 100644 index 00000000..83905651 --- /dev/null +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/email/EmailTokenVerifyScreen.kt @@ -0,0 +1,59 @@ +package com.everymeal.presentation.ui.signup.school.email + +import android.util.Log +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.everymeal.presentation.R +import com.everymeal.presentation.components.EveryMealMainButton +import com.everymeal.presentation.components.EveryMealTextField +import com.everymeal.presentation.ui.signup.school.SchoolAuthViewModel +import com.everymeal.presentation.ui.signup.school.SchoolContract +import com.everymeal.presentation.ui.theme.EveryMealTypography +import com.everymeal.presentation.ui.theme.Gray100 +import com.everymeal.presentation.ui.theme.Gray900 + +@Composable +fun EmailTokenVerifyScreen( + modifier: Modifier, + state: SchoolContract.State, + viewModel: SchoolAuthViewModel +) { + Column( + modifier = modifier.padding(top = 48.dp) + ) { + Text( + text = stringResource(id = R.string.email_token_verify_title), + style = EveryMealTypography.Heading1, + color = Gray900 + ) + Spacer(modifier = Modifier.size(40.dp)) + Text( + text = stringResource(id = R.string.verify_token), + style = EveryMealTypography.Body5, + color = Gray100 + ) + Spacer(modifier = Modifier.size(6.dp)) + EveryMealTextField( + modifier = Modifier.fillMaxWidth(), + value = state.emailAuthValue, + onValueChange = { + viewModel.setEvent(SchoolContract.Event.OnTokenTextChanged(it)) + }, + ) + Spacer(modifier = Modifier.weight(1f)) + EveryMealMainButton( + text = stringResource(id = R.string.next), + onClick = { + viewModel.setEvent(SchoolContract.Event.OnTokenNextButtonClicked) + }, + ) + } +} diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/email/SchoolAuthPostEmailScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/email/SchoolAuthPostEmailScreen.kt index 76f234df..462daf65 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/email/SchoolAuthPostEmailScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/email/SchoolAuthPostEmailScreen.kt @@ -13,7 +13,6 @@ import androidx.compose.ui.unit.dp import com.everymeal.presentation.R import com.everymeal.presentation.components.EveryMealMainButton import com.everymeal.presentation.components.EveryMealTextField -import com.everymeal.presentation.ui.signup.school.SchoolAuthScreenType import com.everymeal.presentation.ui.signup.school.SchoolAuthViewModel import com.everymeal.presentation.ui.signup.school.SchoolContract import com.everymeal.presentation.ui.theme.EveryMealTypography @@ -31,19 +30,13 @@ fun SchoolAuthPostEmailScreen( modifier = modifier.padding(top = 48.dp) ) { Text( - text = when (state.schoolAuthScreenType) { - SchoolAuthScreenType.POST_EMAIL -> stringResource(id = R.string.school_auth_content) - SchoolAuthScreenType.VERIFY_TOKEN -> stringResource(id = R.string.email_token_verify_title) - }, + text = stringResource(id = R.string.school_auth_content), style = EveryMealTypography.Heading1, color = Gray900 ) Spacer(modifier = Modifier.size(40.dp)) Text( - text = when (state.schoolAuthScreenType) { - SchoolAuthScreenType.POST_EMAIL -> stringResource(id = R.string.email) - SchoolAuthScreenType.VERIFY_TOKEN -> stringResource(id = R.string.verify_token) - }, + text = stringResource(id = R.string.email), style = EveryMealTypography.Body5, color = Gray100 ) @@ -52,22 +45,10 @@ fun SchoolAuthPostEmailScreen( modifier = Modifier.fillMaxWidth(), value = state.emailText, onValueChange = { - when (state.schoolAuthScreenType) { - SchoolAuthScreenType.POST_EMAIL -> viewModel.setEvent( - SchoolContract.Event.OnEmailTextChanged( - it - ) - ) - - SchoolAuthScreenType.VERIFY_TOKEN -> viewModel.setEvent( - SchoolContract.Event.OnTokenTextChanged( - it - ) - ) - } + viewModel.setEvent(SchoolContract.Event.OnEmailTextChanged(it)) }, supportingText = { - if (state.isEmailError && state.schoolAuthScreenType == SchoolAuthScreenType.POST_EMAIL) { + if (state.isEmailError) { Text( text = stringResource(id = R.string.email_error), style = EveryMealTypography.Body5, @@ -80,10 +61,7 @@ fun SchoolAuthPostEmailScreen( EveryMealMainButton( text = stringResource(id = R.string.next), onClick = { - when (state.schoolAuthScreenType) { - SchoolAuthScreenType.POST_EMAIL -> viewModel.setEvent(SchoolContract.Event.OnEmailNextButtonClicked) - SchoolAuthScreenType.VERIFY_TOKEN -> viewModel.setEvent(SchoolContract.Event.OnTokenNextButtonClicked) - } + viewModel.setEvent(SchoolContract.Event.OnEmailNextButtonClicked) }, ) }