-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feat] 7주차 필수과제 #13
base: week6-basic
Are you sure you want to change the base?
[Feat] 7주차 필수과제 #13
Changes from all commits
3c5bf49
0113d15
89f3a3c
4e2f696
e4ff3bd
29516b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package org.sopt.and.presentation.ui.auth.screen | ||
|
||
import org.sopt.and.presentation.util.base.UiEvent | ||
import org.sopt.and.presentation.util.base.UiSideEffect | ||
import org.sopt.and.presentation.util.base.UiState | ||
|
||
class AuthContract { | ||
data class AuthUiState( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
val signInState: SignInState = SignInState.Idle, | ||
val signInUsername: String = "", | ||
val signInPassword: String = "", | ||
val signUpState: SignUpState = SignUpState.Idle, | ||
val signUpUsername: String = "", | ||
val signUpPassword: String = "", | ||
val signUpHobby: String = "", | ||
) : UiState | ||
|
||
sealed class AuthEvent : UiEvent { | ||
data class UpdateSignInUsername(val signInUsername: String) : AuthEvent() | ||
data class UpdateSignInPassword(val signInPassword: String) : AuthEvent() | ||
Comment on lines
+19
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1번에 대해서 말씀드리자면,
와 같이 하나의 함수에서 처리할 수 있는 것 불필요한 객체를 참조한다고 여겨져서 혹시 Event에 사용 목적이 따로있으신가요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 값이 바뀔 때 다른 무언가 처리를 한다거나 해야한다면 이벤트를 쓰는 것도 좋다고 생각하는데 아직은 잘 안보여서요 |
||
data class OnSignInClicked(val signInState: SignInState) : AuthEvent() | ||
data object ResetSignInState : AuthEvent() | ||
data class UpdateSignUpUsername(val signUpUsername: String) : AuthEvent() | ||
data class UpdateSignUpPassword(val signUpPassword: String) : AuthEvent() | ||
data class UpdateSignUpHobby(val signUpHobby: String) : AuthEvent() | ||
data class OnSignUpClicked(val signUpState: SignUpState) : AuthEvent() | ||
data object ResetSignUpState : AuthEvent() | ||
} | ||
|
||
sealed interface AuthSideEffect : UiSideEffect { | ||
data object NavigateToSignIn : AuthSideEffect | ||
data object NavigateToSignUp : AuthSideEffect | ||
data object NavigateToMain : AuthSideEffect | ||
data class ShowToast(val message: String) : AuthSideEffect | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,73 +1,115 @@ | ||
package org.sopt.and.presentation.ui.auth.screen | ||
|
||
import android.util.Log | ||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import dagger.hilt.android.lifecycle.HiltViewModel | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.launch | ||
import org.sopt.and.domain.model.User | ||
import org.sopt.and.domain.repository.AuthRepository | ||
import org.sopt.and.domain.repository.TokenRepository | ||
import org.sopt.and.presentation.util.base.BaseViewModel | ||
import javax.inject.Inject | ||
|
||
@HiltViewModel | ||
class AuthViewModel @Inject constructor( | ||
private val tokenRepository: TokenRepository, | ||
private val authRepository: AuthRepository, | ||
) : ViewModel() { | ||
private val _signInState = MutableStateFlow<SignInState>(SignInState.Idle) | ||
val signInState: StateFlow<SignInState> = _signInState | ||
) : BaseViewModel<AuthContract.AuthUiState, AuthContract.AuthSideEffect, AuthContract.AuthEvent>() { | ||
override fun createInitialState(): AuthContract.AuthUiState = AuthContract.AuthUiState() | ||
|
||
private val _token = MutableStateFlow("") | ||
val token: StateFlow<String> = _token | ||
override suspend fun handleEvent(event: AuthContract.AuthEvent) { | ||
when (event) { | ||
is AuthContract.AuthEvent.UpdateSignInUsername -> setState { | ||
copy(signInUsername = event.signInUsername) | ||
} | ||
is AuthContract.AuthEvent.UpdateSignInPassword -> setState { | ||
copy(signInPassword = event.signInPassword) | ||
} | ||
is AuthContract.AuthEvent.OnSignInClicked -> setState { | ||
copy(signInState = event.signInState) | ||
} | ||
is AuthContract.AuthEvent.ResetSignInState -> setState { | ||
copy(signInState = SignInState.Idle) | ||
} | ||
is AuthContract.AuthEvent.UpdateSignUpUsername -> setState { | ||
copy(signUpUsername = event.signUpUsername) | ||
} | ||
is AuthContract.AuthEvent.UpdateSignUpPassword -> setState { | ||
copy(signUpPassword = event.signUpPassword) | ||
} | ||
Comment on lines
+33
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
is AuthContract.AuthEvent.UpdateSignUpHobby -> setState { | ||
copy(signUpHobby = event.signUpHobby) | ||
} | ||
is AuthContract.AuthEvent.OnSignUpClicked -> setState { | ||
copy(signUpState = event.signUpState) | ||
} | ||
is AuthContract.AuthEvent.ResetSignUpState -> setState { | ||
copy(signUpState = SignUpState.Idle) | ||
} | ||
} | ||
} | ||
|
||
private val _signUpState = MutableStateFlow<SignUpState>(SignUpState.Idle) | ||
val signUpState: StateFlow<SignUpState> = _signUpState | ||
fun updateSignInUsername(input: String) { | ||
viewModelScope.launch { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. viewModelScope로 감싼 이유가 있나요? |
||
setEvent(AuthContract.AuthEvent.UpdateSignInUsername(signInUsername = input)) | ||
} | ||
} | ||
|
||
fun validateSignIn(username: String, password: String) { | ||
fun updateSignInPassword(input: String) { | ||
viewModelScope.launch { | ||
_signInState.value = SignInState.Loading | ||
val result = authRepository.login(username = username, password = password) | ||
_signInState.value = result.fold( | ||
setEvent(AuthContract.AuthEvent.UpdateSignInPassword(signInPassword = input)) | ||
} | ||
} | ||
|
||
fun validateSignIn() { | ||
viewModelScope.launch { | ||
setEvent(AuthContract.AuthEvent.OnSignInClicked(signInState = SignInState.Loading)) | ||
authRepository.login(username = currentState.signInUsername, password = currentState.signInPassword).fold( | ||
onSuccess = { token -> | ||
tokenRepository.setToken(token.token) | ||
SignInState.Success(token) | ||
setEvent(AuthContract.AuthEvent.OnSignInClicked(signInState = SignInState.Success(result = token))) | ||
}, | ||
onFailure = { | ||
SignInState.Failure(it.localizedMessage ?: "에러 발생") | ||
setEvent(AuthContract.AuthEvent.OnSignInClicked(signInState = SignInState.Failure(errorMessage = it.localizedMessage?: ""))) | ||
} | ||
) | ||
} | ||
} | ||
|
||
fun validateSignUp(username: String, password: String, hobby: String) { | ||
fun updateSignUpUsername(input: String) { | ||
viewModelScope.launch { | ||
setEvent(AuthContract.AuthEvent.UpdateSignUpUsername(signUpUsername = input)) | ||
} | ||
} | ||
|
||
fun updateSignUpPassword(input: String) { | ||
viewModelScope.launch { | ||
_signUpState.value = SignUpState.Loading | ||
val result = authRepository.registerUser( | ||
setEvent(AuthContract.AuthEvent.UpdateSignUpPassword(signUpPassword = input)) | ||
} | ||
} | ||
|
||
fun updateSignUpHobby(input: String) { | ||
viewModelScope.launch { | ||
setEvent(AuthContract.AuthEvent.UpdateSignUpHobby(signUpHobby = input)) | ||
} | ||
} | ||
|
||
fun validateSignUp() { | ||
viewModelScope.launch { | ||
setEvent(AuthContract.AuthEvent.OnSignUpClicked(signUpState = SignUpState.Loading)) | ||
authRepository.registerUser( | ||
user = User( | ||
username = username, | ||
password = password, | ||
hobby = hobby | ||
username = currentState.signUpUsername, | ||
password = currentState.signUpPassword, | ||
hobby = currentState.signUpHobby | ||
) | ||
) | ||
_signUpState.value = result.fold( | ||
onSuccess = { | ||
SignUpState.Success(it) | ||
).fold( | ||
onSuccess = { userNo -> | ||
setEvent(AuthContract.AuthEvent.OnSignUpClicked(signUpState = SignUpState.Success(userNo))) | ||
}, | ||
onFailure = { | ||
SignUpState.Failure(it.localizedMessage ?: "에러 발생") | ||
setEvent(AuthContract.AuthEvent.OnSignUpClicked(signUpState = SignUpState.Failure(it.localizedMessage ?: ""))) | ||
} | ||
) | ||
} | ||
} | ||
|
||
fun resetSignInState() { | ||
_signInState.value = SignInState.Idle | ||
} | ||
|
||
fun resetSignUpState() { | ||
_signUpState.value = SignUpState.Idle | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,11 +18,8 @@ import androidx.compose.material3.ButtonDefaults | |
import androidx.compose.material3.Text | ||
import androidx.compose.material3.VerticalDivider | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.collectAsState | ||
import androidx.compose.runtime.LaunchedEffect | ||
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.draw.clip | ||
|
@@ -36,10 +33,13 @@ import androidx.compose.ui.tooling.preview.Preview | |
import androidx.compose.ui.unit.dp | ||
import androidx.compose.ui.unit.sp | ||
import androidx.hilt.navigation.compose.hiltViewModel | ||
import androidx.lifecycle.compose.LocalLifecycleOwner | ||
import androidx.lifecycle.compose.collectAsStateWithLifecycle | ||
import androidx.lifecycle.flowWithLifecycle | ||
import org.sopt.and.R | ||
import org.sopt.and.presentation.ui.common.WavveTextField | ||
import org.sopt.and.presentation.ui.auth.component.SocialPlatformIconRow | ||
import org.sopt.and.presentation.ui.auth.component.SocialPlatformList | ||
import org.sopt.and.presentation.ui.common.WavveTextField | ||
import org.sopt.and.presentation.util.showToast | ||
import org.sopt.and.ui.theme.ANDANDROIDTheme | ||
|
||
|
@@ -49,29 +49,60 @@ fun SignInRoute( | |
navigateToSignUp: () -> Unit, | ||
navigateToMain: () -> Unit, | ||
) { | ||
val signInState by authViewModel.signInState.collectAsState() | ||
val authUiState by authViewModel.uiState.collectAsStateWithLifecycle() | ||
val lifecycleOwner = LocalLifecycleOwner.current | ||
val context = LocalContext.current | ||
|
||
LaunchedEffect(authViewModel.sideEffect, lifecycleOwner) { | ||
authViewModel.sideEffect.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle) | ||
.collect { authSideEffect -> | ||
when (authSideEffect) { | ||
is AuthContract.AuthSideEffect.NavigateToSignUp -> navigateToSignUp() | ||
is AuthContract.AuthSideEffect.NavigateToMain -> navigateToMain() | ||
is AuthContract.AuthSideEffect.ShowToast -> showToast( | ||
context = context, | ||
message = authSideEffect.message | ||
) | ||
|
||
else -> {} | ||
} | ||
} | ||
} | ||
|
||
LaunchedEffect(authUiState.signInState) { | ||
when (authUiState.signInState) { | ||
is SignInState.Success -> { | ||
authViewModel.setSideEffect(AuthContract.AuthSideEffect.ShowToast(message = "로그인에 성공하였습니다.")) | ||
authViewModel.setEvent(AuthContract.AuthEvent.ResetSignInState) | ||
authViewModel.setSideEffect(AuthContract.AuthSideEffect.NavigateToMain) | ||
} | ||
|
||
is SignInState.Failure -> { | ||
authViewModel.setSideEffect(AuthContract.AuthSideEffect.ShowToast(message = "아이디와 비밀번호를 다시 확인해주세요.")) | ||
authViewModel.setEvent(AuthContract.AuthEvent.ResetSignInState) | ||
} | ||
|
||
else -> {} | ||
} | ||
} | ||
|
||
SignInScreen( | ||
signInState = signInState, | ||
resetSignInState = { authViewModel.resetSignInState() }, | ||
onSignUpClick = navigateToSignUp, | ||
onSignInClick = { username, password -> authViewModel.validateSignIn(username, password) }, | ||
navigateToMain = navigateToMain | ||
authUiState = authUiState, | ||
updateSignInUsername = { input -> authViewModel.updateSignInUsername(input) }, | ||
updateSignInPassword = { input -> authViewModel.updateSignInPassword(input) }, | ||
onSignInClick = { authViewModel.validateSignIn() }, | ||
onSignUpClick = { authViewModel.setSideEffect(AuthContract.AuthSideEffect.NavigateToSignUp) }, | ||
) | ||
} | ||
|
||
@Composable | ||
fun SignInScreen( | ||
signInState: SignInState, | ||
resetSignInState: () -> Unit, | ||
authUiState: AuthContract.AuthUiState, | ||
updateSignInUsername: (String) -> Unit, | ||
updateSignInPassword: (String) -> Unit, | ||
onSignInClick: () -> Unit, | ||
onSignUpClick: () -> Unit, | ||
onSignInClick: (String, String) -> Unit, | ||
navigateToMain: () -> Unit, | ||
) { | ||
val context = LocalContext.current | ||
var inputEmail by remember { mutableStateOf("") } | ||
var inputPassword by remember { mutableStateOf("") } | ||
|
||
Column( | ||
modifier = Modifier | ||
.fillMaxSize() | ||
|
@@ -105,8 +136,8 @@ fun SignInScreen( | |
Spacer(Modifier.height(40.dp)) | ||
|
||
WavveTextField( | ||
value = inputEmail, | ||
onValueChange = { newValue -> inputEmail = newValue }, | ||
value = authUiState.signInUsername, | ||
onValueChange = updateSignInUsername, | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.clip(shape = RoundedCornerShape(6.dp)) | ||
|
@@ -115,8 +146,8 @@ fun SignInScreen( | |
) | ||
Spacer(Modifier.height(4.dp)) | ||
WavveTextField( | ||
value = inputPassword, | ||
onValueChange = { newValue -> inputPassword = newValue }, | ||
value = authUiState.signInPassword, | ||
onValueChange = updateSignInPassword, | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.clip(shape = RoundedCornerShape(6.dp)) | ||
|
@@ -128,7 +159,7 @@ fun SignInScreen( | |
Spacer(Modifier.height(30.dp)) | ||
|
||
Button( | ||
onClick = { onSignInClick(inputEmail, inputPassword) }, | ||
onClick = onSignInClick, | ||
modifier = Modifier.fillMaxWidth(), | ||
colors = ButtonDefaults.buttonColors(Color(0xFF0F42C7)) | ||
) { | ||
|
@@ -207,7 +238,8 @@ fun SignInScreen( | |
color = Color(0xFF5D5D5D) | ||
) | ||
Text( | ||
text = "SNS계정으로 간편하게 가입하여 서비스를 이용하실 수 있습니다.\n기존 POOQ 계정 또는 Wavve 계정과는 연동되지 않으니 이용에 참고하세요", | ||
text = "SNS계정으로 간편하게 가입하여 서비스를 이용하실 수 있습니다.\n" + | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 스트링 값 분리하면 더 좋읗 거 같아요 |
||
"기존 POOQ 계정 또는 Wavve 계정과는 연동되지 않으니 이용에 참고하세요", | ||
modifier = Modifier.fillMaxWidth(), | ||
fontSize = 10.sp, | ||
lineHeight = 14.sp, | ||
|
@@ -216,27 +248,6 @@ fun SignInScreen( | |
} | ||
|
||
Spacer(modifier = Modifier.weight(1f)) | ||
|
||
when (signInState) { | ||
is SignInState.Success -> { | ||
showToast( | ||
context = context, | ||
message = "로그인에 성공했습니다." | ||
) | ||
resetSignInState() | ||
navigateToMain() | ||
} | ||
|
||
is SignInState.Failure -> { | ||
showToast( | ||
context = context, | ||
message = "아이디와 비밀번호를 다시 확인해주세요." | ||
) | ||
resetSignInState() | ||
} | ||
|
||
else -> {} | ||
} | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 State, Event, SideEffect, 뿐만 아니라 SignIn, SignUp까지 다 분리하는 것이 좋다(?) 라고 생각하고 구현을 했었는데, 재원이 오빠도 그렇고 민재오빠도 이렇게 구현한 걸 보고 더 가독성이 좋고, 한눈에 보기 편하다는 생각이 들었어요! 주요 기능에 따라 이렇게 묶어둔 것 보고 리팩토링 때 이렇게 적용해보고자 해요 ㅎ 감삼둥