-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT] 로그인 성공 후 개인정보 동의 #37
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
Changes from 18 commits
26242cc
1b72cd6
779b88b
f2b98ac
a8217fc
5eebc95
a897fff
f9d53ca
0315e36
e779a6c
ea3f273
7bb890f
3e10513
2a67004
eb09411
aa0e2d3
dc1891b
308dcd6
fa024c7
14f66ab
d73e53d
7a592e1
2125d54
3ef8a7a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,12 +4,17 @@ import androidx.lifecycle.ViewModel | |
| import androidx.lifecycle.viewModelScope | ||
| import com.alarmy.near.data.repository.AuthRepository | ||
| import com.alarmy.near.model.ProviderType | ||
| import com.alarmy.near.presentation.feature.login.model.TermType | ||
| import dagger.hilt.android.lifecycle.HiltViewModel | ||
| import kotlinx.coroutines.channels.Channel | ||
| import kotlinx.coroutines.delay | ||
| import kotlinx.coroutines.flow.MutableStateFlow | ||
| import kotlinx.coroutines.flow.SharingStarted | ||
| import kotlinx.coroutines.flow.StateFlow | ||
| import kotlinx.coroutines.flow.asStateFlow | ||
| import kotlinx.coroutines.flow.map | ||
| import kotlinx.coroutines.flow.receiveAsFlow | ||
| import kotlinx.coroutines.flow.stateIn | ||
| import kotlinx.coroutines.launch | ||
| import javax.inject.Inject | ||
|
|
||
|
|
@@ -23,45 +28,193 @@ class LoginViewModel | |
| private val _uiState = MutableStateFlow(LoginUiState()) | ||
| val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow() | ||
|
|
||
| // 에러 이벤트 관리 | ||
| private val _errorEvent = Channel<Throwable?>() | ||
| val errorEvent = _errorEvent.receiveAsFlow() | ||
| // 이벤트 관리 | ||
| private val _event = Channel<LoginEvent>() | ||
| val event = _event.receiveAsFlow() | ||
|
|
||
| // 로그인 성공 이벤트 관리 | ||
| private val _loginSuccessEvent = Channel<Unit>() | ||
| val loginSuccessEvent = _loginSuccessEvent.receiveAsFlow() | ||
| // 로그인 화면 상태 관리 | ||
| private val _loginState = MutableStateFlow(LoginState()) | ||
| val loginState: StateFlow<LoginState> = _loginState.asStateFlow() | ||
|
|
||
| // 개별 상태들을 편의를 위해 노출 | ||
| val termsAgreementState: StateFlow<TermsAgreementState> = | ||
| _loginState | ||
| .map { it.termsAgreementState } | ||
| .stateIn( | ||
| viewModelScope, | ||
| SharingStarted.WhileSubscribed(), | ||
| TermsAgreementState(), | ||
| ) | ||
| val showPrivacyBottomSheet: StateFlow<Boolean> = | ||
| _loginState | ||
| .map { it.showPrivacyBottomSheet } | ||
| .stateIn( | ||
| viewModelScope, | ||
| SharingStarted.WhileSubscribed(), | ||
| false, | ||
| ) | ||
|
|
||
| /** | ||
| * 소셜 로그인 수행 | ||
| */ | ||
| fun performLogin(providerType: ProviderType) { | ||
| viewModelScope.launch { | ||
| updateLoadingState(isLoading = true) | ||
|
|
||
| authRepository.performSocialLogin(providerType) | ||
| authRepository | ||
| .performSocialLogin(providerType) | ||
| .onSuccess { | ||
| updateLoadingState(isLoading = false) | ||
| _loginSuccessEvent.send(Unit) | ||
| } | ||
| .onFailure { exception -> | ||
| _loginState.value = _loginState.value.copy(showPrivacyBottomSheet = true) | ||
| }.onFailure { exception -> | ||
| updateLoadingState(isLoading = false) | ||
| _errorEvent.send(exception) | ||
| _event.send(LoginEvent.ShowError(exception)) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 개인정보 동의 완료 처리 | ||
| */ | ||
| fun onPrivacyConsentComplete() { | ||
| viewModelScope.launch { | ||
| _loginState.value = _loginState.value.copy(showPrivacyBottomSheet = false) | ||
| _event.send(LoginEvent.NavigateToHome) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 프라이버시 바텀시트 닫기 | ||
| */ | ||
| fun dismissPrivacyBottomSheet() { | ||
| _loginState.value = _loginState.value.copy(showPrivacyBottomSheet = false) | ||
| } | ||
|
|
||
| /** | ||
| * 로딩 상태 업데이트 | ||
| */ | ||
| private fun updateLoadingState(isLoading: Boolean) { | ||
| _uiState.value = _uiState.value.copy(isLoading = isLoading) | ||
| } | ||
|
|
||
| /** | ||
| * 약관 전체 동의 토글 | ||
| */ | ||
| fun toggleAllTermsAgreement() { | ||
| val currentTermsState = _loginState.value.termsAgreementState | ||
| val newAgreedState = !currentTermsState.isAllAgreed | ||
|
|
||
| val updatedTermsState = | ||
| currentTermsState.copy( | ||
| isAllAgreed = newAgreedState, | ||
| isServiceTermsAgreed = newAgreedState, | ||
| isPrivacyCollectionAgreed = newAgreedState, | ||
| isPrivacyPolicyAgreed = newAgreedState, | ||
| ) | ||
|
|
||
| _loginState.value = _loginState.value.copy(termsAgreementState = updatedTermsState) | ||
| } | ||
|
|
||
| /** | ||
| * 개별 약관 동의 토글 | ||
| */ | ||
| fun toggleIndividualTermsAgreement(termType: TermType) { | ||
| val currentTermsState = _loginState.value.termsAgreementState | ||
| val newTermsState = | ||
| when (termType) { | ||
| TermType.SERVICE_TERMS -> currentTermsState.copy(isServiceTermsAgreed = !currentTermsState.isServiceTermsAgreed) | ||
| TermType.PRIVACY_COLLECTION -> | ||
| currentTermsState.copy( | ||
| isPrivacyCollectionAgreed = !currentTermsState.isPrivacyCollectionAgreed, | ||
| ) | ||
|
|
||
| TermType.PRIVACY_POLICY -> currentTermsState.copy(isPrivacyPolicyAgreed = !currentTermsState.isPrivacyPolicyAgreed) | ||
| } | ||
|
|
||
| // 모든 개별 약관이 동의되었는지 확인하여 전체 동의 상태 업데이트 | ||
| val isAllIndividualAgreed = | ||
| newTermsState.isServiceTermsAgreed && | ||
| newTermsState.isPrivacyCollectionAgreed && | ||
| newTermsState.isPrivacyPolicyAgreed | ||
|
|
||
| val updatedTermsState = newTermsState.copy(isAllAgreed = isAllIndividualAgreed) | ||
| _loginState.value = _loginState.value.copy(termsAgreementState = updatedTermsState) | ||
| } | ||
|
|
||
| /** | ||
| * 약관 상세 보기 (웹뷰로 이동) | ||
| */ | ||
| fun showTermsDetail(termType: TermType) { | ||
| viewModelScope.launch { | ||
| _loginState.value = | ||
| _loginState.value.copy( | ||
| showPrivacyBottomSheet = false, | ||
| hasNavigatedToWebView = true, | ||
| ) | ||
| delay(500) | ||
|
||
| _event.send(LoginEvent.ShowTermsDetail(termType)) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 웹뷰에서 돌아온 후 바텀시트 복원 | ||
| */ | ||
| fun restoreBottomSheetIfNeeded() { | ||
| val currentState = _loginState.value | ||
| if (currentState.hasNavigatedToWebView && !currentState.showPrivacyBottomSheet) { | ||
| _loginState.value = | ||
| currentState.copy( | ||
| showPrivacyBottomSheet = true, | ||
| hasNavigatedToWebView = false, | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 로그인 화면 통합 상태 | ||
| */ | ||
| data class LoginState( | ||
| val termsAgreementState: TermsAgreementState = TermsAgreementState(), | ||
| val hasNavigatedToWebView: Boolean = false, | ||
| val showPrivacyBottomSheet: Boolean = false, | ||
| ) | ||
|
|
||
| /** | ||
| * 로그인 화면 UI 상태 | ||
| */ | ||
| data class LoginUiState( | ||
| val isLoading: Boolean = false, | ||
| val hasError: Boolean = false, | ||
| ) | ||
|
|
||
| /** | ||
| * 약관 동의 상태 | ||
| */ | ||
| data class TermsAgreementState( | ||
| val isAllAgreed: Boolean = false, | ||
| val isServiceTermsAgreed: Boolean = false, | ||
| val isPrivacyCollectionAgreed: Boolean = false, | ||
| val isPrivacyPolicyAgreed: Boolean = false, | ||
| ) { | ||
| /** | ||
| * 모든 필수 약관에 동의했는지 확인 | ||
| */ | ||
| val isAllRequiredTermsAgreed: Boolean | ||
| get() = isServiceTermsAgreed && isPrivacyCollectionAgreed && isPrivacyPolicyAgreed | ||
| } | ||
|
|
||
| /* | ||
| * 로그인 화면 이벤트 관리 | ||
| * | ||
| * */ | ||
| sealed class LoginEvent { | ||
| object NavigateToHome : LoginEvent() | ||
|
|
||
| data class ShowTermsDetail( | ||
| val termType: TermType, | ||
| ) : LoginEvent() | ||
|
|
||
| data class ShowError( | ||
| val throwable: Throwable?, | ||
| ) : LoginEvent() | ||
| } | ||
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.
로그인 에러 발생 시 처리가
TODO()로 남아있습니다. 이 경우 앱이 비정상적으로 동작하거나 사용자가 문제를 인지하지 못할 수 있습니다.LoginRoute에 에러 처리를 위한 콜백(예:onShowErrorSnackBar)을 추가하고, 이 곳에서 해당 콜백을 호출하여 사용자에게 스낵바 등으로 에러 상황을 알려주는 것이 좋습니다.