-
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
Conversation
- 로그인 성공, 에러 이벤트를 `LoginEvent` sealed class로 통합하여 관리 - 약관동의를 위한 ShowPrivacyBottomShhet Event 추가
- 사용자가 소셜 로그인 시, 약관에 동의할 수 있도록 바텀시트 UI를 구현했습니다. - `PrivacyConsentBottomSheet`와 `TermsAgreementItem` Composable을 추가하여 약관 동의 화면을 구성했습니다. - 로그인 성공 후 약관 동의 바텀시트가 나타나고, 동의 완료 시 홈 화면으로 이동하는 로직을 `LoginScreen`에 추가했습니다.
- `NearCheckbox` 컴포넌트의 클릭 효과에서 물결(ripple) 효과를 제거하기 위해 `clickable`을 `onNoRippleClick` 확장 함수로 변경했습니다.
- 약관 동의 상태를 관리하기 위한 `TermsAgreementState` 데이터 클래스와 `TermType` enum을 추가했습니다. - `LoginViewModel`에 약관 전체 동의 및 개별 동의 토글 로직, 약관 상세 보기 이벤트 처리 로직을 구현했습니다. - 필수 약관에 모두 동의해야 '가입' 버튼이 활성화되도록 수정했습니다. - 약관 텍스트 영역 클릭 시 약관 상세 웹뷰로 이동하는 이벤트를 추가했습니다. - `NearBottomSheetDragHandle` 컴포저블을 추가하여 BottomSheet의 드래그 핸들을 공통 컴포넌트로 분리했습니다.
- 소셜 로그인 성공 후 홈 화면으로 바로 이동하던 로직을 수정하여, 약관 동의 BottomSheet가 먼저 표시되도록 변경했습니다. - 약관 동의가 완료된 후 홈 화면으로 이동하는 `onPrivacyConsentComplete` 함수를 추가했습니다.
- 로그인 성공, 에러 이벤트를 `LoginEvent` sealed class로 통합하여 관리 - 약관동의를 위한 ShowPrivacyBottomShhet Event 추가
- 사용자가 소셜 로그인 시, 약관에 동의할 수 있도록 바텀시트 UI를 구현했습니다. - `PrivacyConsentBottomSheet`와 `TermsAgreementItem` Composable을 추가하여 약관 동의 화면을 구성했습니다. - 로그인 성공 후 약관 동의 바텀시트가 나타나고, 동의 완료 시 홈 화면으로 이동하는 로직을 `LoginScreen`에 추가했습니다.
- `NearCheckbox` 컴포넌트의 클릭 효과에서 물결(ripple) 효과를 제거하기 위해 `clickable`을 `onNoRippleClick` 확장 함수로 변경했습니다.
- 약관 동의 상태를 관리하기 위한 `TermsAgreementState` 데이터 클래스와 `TermType` enum을 추가했습니다. - `LoginViewModel`에 약관 전체 동의 및 개별 동의 토글 로직, 약관 상세 보기 이벤트 처리 로직을 구현했습니다. - 필수 약관에 모두 동의해야 '가입' 버튼이 활성화되도록 수정했습니다. - 약관 텍스트 영역 클릭 시 약관 상세 웹뷰로 이동하는 이벤트를 추가했습니다. - `NearBottomSheetDragHandle` 컴포저블을 추가하여 BottomSheet의 드래그 핸들을 공통 컴포넌트로 분리했습니다.
- 소셜 로그인 성공 후 홈 화면으로 바로 이동하던 로직을 수정하여, 약관 동의 BottomSheet가 먼저 표시되도록 변경했습니다. - 약관 동의가 완료된 후 홈 화면으로 이동하는 `onPrivacyConsentComplete` 함수를 추가했습니다.
- 우측 끝에 > 이미지를 추가합니다
- TermType에 titleRes, url 추가 - LoginScreen에서 약관 상세 보기 클릭 시 웹뷰로 이동하도록 수정 - PrivacyBottomSheet에서 TermType의 titleRes를 사용하도록 수정 - NearNavHost에 onNavigateToTerms 추가하여 웹뷰 이동 로직 연결
- `TermType` enum 클래스를 `LoginViewModel.kt`에서 `login/model/TermType.kt` 파일로 분리했습니다. - `LoginScreen.kt`, `LoginViewModel.kt`, `PrivacyBottomSheet.kt`에서 분리된 `TermType` 클래스를 참조하도록 수정했습니다.
- `PrivacyBottomSheet.kt`에서 하드코딩된 문자열들을 `strings.xml`에 정의된 리소스로 변경했습니다. - `strings.xml`에 개인정보 동의 바텀시트 관련 문자열들을 추가했습니다.
- LoginViewModel에서 약관 동의 및 바텀시트 상태를 LoginState로 통합 관리하도록 변경했습니다. - 웹뷰에서 돌아올 때 개인정보처리방침 바텀시트가 다시 보이도록 로직을 추가했습니다. - LoginScreen에서 Lifecycle 이벤트를 감지하여 바텀시트 복원 로직을 호출하도록 수정했습니다.
- `PrivacyBottomSheet.kt`에서 사용하던 약관 항목 `Triple`을 `TermsItem` 데이터 클래스로 분리했습니다.
…gin-privacy # Conflicts: # Near/app/src/main/java/com/alarmy/near/presentation/feature/login/LoginScreen.kt # Near/app/src/main/java/com/alarmy/near/presentation/feature/login/LoginViewModel.kt # Near/app/src/main/java/com/alarmy/near/presentation/feature/login/PrivacyBottomSheet.kt # Near/app/src/main/java/com/alarmy/near/presentation/feature/login/components/NearBottomSheetDragHandle.kt # Near/app/src/main/java/com/alarmy/near/presentation/feature/login/components/TermsAgreementItem.kt
Summary of ChangesHello @stopstone, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 PR은 로그인 성공 후 사용자에게 개인정보 동의 절차를 도입하여 서비스 이용 전 필수 약관 동의를 받도록 합니다. 이를 위해 새로운 바텀시트 UI와 관련 로직을 구현했으며, 사용자가 약관 상세 내용을 웹뷰로 확인할 수 있도록 하고 웹뷰에서 복귀 시 바텀시트 상태를 유지하는 기능을 포함합니다. 전반적인 로그인 화면의 상태 관리도 개선되었습니다. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
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.
Code Review
로그인 성공 후 개인정보 동의를 받는 기능이 추가되었네요. 전체적으로 ViewModel에서 상태를 관리하고, Composable에서 이를 구독하여 UI를 그리는 패턴을 잘 따르고 있습니다. 특히 LoginEvent sealed class를 도입하여 이벤트를 하나로 통합한 점과, 웹뷰에서 돌아왔을 때 바텀시트 상태를 복원하는 로직을 구현한 점이 인상적입니다. 몇 가지 개선할 수 있는 부분에 대해 리뷰를 남겼으니 확인 부탁드립니다.
| onNavigateToWebView(title, event.termType.url) | ||
| } | ||
|
|
||
| is LoginEvent.ShowError -> TODO() |
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.
| showPrivacyBottomSheet = false, | ||
| hasNavigatedToWebView = true, | ||
| ) | ||
| delay(500) |
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.
애니메이션 완료를 기다리기 위해 고정된 delay(500)를 사용하는 것은 불안정한 방법입니다. 사용자의 기기 성능이나 애니메이션 설정에 따라 UI가 깨져보일 수 있습니다.
가장 간단한 해결책은 delay를 제거하는 것입니다. 즉시 화면을 전환하더라도 showPrivacyBottomSheet 상태가 false로 변경되었기 때문에 바텀시트는 자연스럽게 사라집니다. 만약 전환 애니메이션이 꼭 필요하다면, Composable의 ModalBottomSheetState가 완전히 사라졌을 때(e.g., !bottomSheetState.isVisible) 이벤트를 발생시켜 화면을 전환하는 더 견고한 방법을 고려해볼 수 있습니다.
| val termsList = listOf( | ||
| TermsItem( | ||
| title = "$requiredPrefix ${stringResource(TermType.SERVICE_TERMS.titleRes)}", | ||
| termType = TermType.SERVICE_TERMS, | ||
| isAgreed = termsAgreementState.isServiceTermsAgreed, | ||
| ), | ||
| TermsItem( | ||
| title = "$requiredPrefix ${stringResource(TermType.PRIVACY_COLLECTION.titleRes)}", | ||
| termType = TermType.PRIVACY_COLLECTION, | ||
| isAgreed = termsAgreementState.isPrivacyCollectionAgreed, | ||
| ), | ||
| TermsItem( | ||
| title = "$requiredPrefix ${stringResource(TermType.PRIVACY_POLICY.titleRes)}", | ||
| termType = TermType.PRIVACY_POLICY, | ||
| isAgreed = termsAgreementState.isPrivacyPolicyAgreed, | ||
| ), | ||
| ) | ||
|
|
||
| Column( | ||
| modifier = | ||
| Modifier | ||
| .fillMaxWidth() | ||
| .border( | ||
| width = 1.dp, | ||
| color = NearTheme.colors.GRAY03_EBEBEB, | ||
| shape = RoundedCornerShape(12.dp), | ||
| ).padding(horizontal = 16.dp, vertical = 4.dp), | ||
| ) { | ||
| termsList.forEachIndexed { index, termsItem -> | ||
| TermsAgreementItem( | ||
| text = termsItem.title, | ||
| isChecked = termsItem.isAgreed, | ||
| onCheckedChange = { onToggleIndividualTerms(termsItem.termType) }, | ||
| onclick = { onShowTermsDetail(termsItem.termType) }, | ||
| showDivider = index < termsList.size - 1, | ||
| ) | ||
| } | ||
| } |
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.
이 TermsAgreementSection Composable이 리컴포지션될 때마다 termsList가 새로 생성되고 있습니다. 여기에는 stringResource 호출과 객체 생성이 포함되어 있어 비효율적일 수 있습니다.
약관 타입(TermType) 목록을 상수로 정의하거나 remember로 감싼 뒤 forEachIndexed 루프 안에서 TermsAgreementItem을 직접 생성하는 방식으로 리팩토링하여 불필요한 객체 생성을 줄이는 것을 제안합니다.
val terms = listOf(
TermType.SERVICE_TERMS,
TermType.PRIVACY_COLLECTION,
TermType.PRIVACY_POLICY,
)
Column(
modifier =
Modifier
.fillMaxWidth()
.border(
width = 1.dp,
color = NearTheme.colors.GRAY03_EBEBEB,
shape = RoundedCornerShape(12.dp),
).padding(horizontal = 16.dp, vertical = 4.dp),
) {
terms.forEachIndexed { index, termType ->
val isAgreed =
when (termType) {
TermType.SERVICE_TERMS -> termsAgreementState.isServiceTermsAgreed
TermType.PRIVACY_COLLECTION -> termsAgreementState.isPrivacyCollectionAgreed
TermType.PRIVACY_POLICY -> termsAgreementState.isPrivacyPolicyAgreed
}
TermsAgreementItem(
text = "$requiredPrefix ${stringResource(termType.titleRes)}",
isChecked = isAgreed,
onCheckedChange = { onToggleIndividualTerms(termType) },
onclick = { onShowTermsDetail(termType) },
showDivider = index < terms.size - 1,
)
}
}| Row( | ||
| modifier = | ||
| Modifier | ||
| .padding(vertical = 14.dp) | ||
| .onNoRippleClick(onclick), | ||
| verticalAlignment = Alignment.CenterVertically, | ||
| horizontalArrangement = Arrangement.SpaceBetween, | ||
| ) { | ||
| // 체크박스 클릭 시 체크 상태만 변경 | ||
| NearCheckbox( | ||
| checked = isChecked, | ||
| onCheckedChange = onCheckedChange, | ||
| ) | ||
|
|
||
| Spacer(modifier = Modifier.size(8.dp)) | ||
|
|
||
| // 텍스트 영역 클릭 시 웹뷰로 이동 | ||
| Text( | ||
| text = text, | ||
| style = NearTheme.typography.B2_14_MEDIUM, | ||
| ) | ||
|
|
||
| Spacer(modifier = Modifier.weight(1f)) | ||
|
|
||
| Image( | ||
| painter = painterResource(R.drawable.ic_front_24_gray), | ||
| contentDescription = null, | ||
| ) | ||
| } |
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.
Row 전체에 onNoRippleClick을 적용하면 체크박스 주변의 빈 공간을 클릭해도 웹뷰로 이동하게 됩니다. 이는 사용자가 의도하지 않은 동작일 수 있습니다.
클릭 영역을 명확히 하기 위해, Text와 화살표 Image를 별도의 Row로 감싸고 해당 Row에만 onNoRippleClick을 적용하는 것을 고려해 보세요. 이렇게 하면 사용자가 웹뷰로 이동하는 영역을 더 직관적으로 인지할 수 있습니다.
Row(
modifier = Modifier.padding(vertical = 14.dp),
verticalAlignment = Alignment.CenterVertically,
) {
// 체크박스 클릭 시 체크 상태만 변경
NearCheckbox(
checked = isChecked,
onCheckedChange = onCheckedChange,
)
Spacer(modifier = Modifier.size(8.dp))
// 텍스트와 화살표 영역 클릭 시 웹뷰로 이동
Row(
modifier = Modifier.weight(1f).onNoRippleClick(onclick),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = text,
style = NearTheme.typography.B2_14_MEDIUM,
)
Spacer(modifier = Modifier.weight(1f))
Image(
painter = painterResource(R.drawable.ic_front_24_gray),
contentDescription = null,
)
}
}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.
현재 Row 자체를 클릭하면 웹뷰로 전환되도록 의도하여 이부분은 수정하지 않겠습니다.
체크박스 부분은 웹뷰로 전환되지 않고 체크만 되고, 다른 Row영역은 클릭되는 것이 맞습니다.
- `TermsItem` 데이터 클래스 사용을 제거하고, `TermType` enum class를 직접 사용하여 약관 항목을 구성하도록 수정했습니다. - 약관 타입 목록(`terms`)을 `remember`를 사용하여 리컴포지션 시 재생성되지 않도록 최적화했습니다.
- `LoginRoute`에 `onShowErrorSnackBar` 콜백을 추가하여 로그인 실패 시 스낵바를 표시하도록 구현했습니다. - `LoginEvent.ShowError` 발생 시 `onShowErrorSnackBar`를 호출하여 에러 메시지를 전달합니다.
- `LoginScreen`에서 약관 클릭 시 웹뷰로 이동하는 `onNavigateToWebView` 콜백을 `PrivacyBottomSheet`에 전달하도록 수정했습니다. - 기존 delay()를 활용한 이유는 바텀시트가 내려가기 전에 웹뷰로 전환되어 딜레이를 호출하였습니다.
- `WebViewFrame.kt`에서 웹뷰 페이지 로딩 중 `CircularProgressIndicator`를 표시하도록 수정했습니다. - 웹뷰 로딩 상태를 관리하기 위해 `isLoading` 상태 변수를 추가했습니다. - `WebViewClient`의 `onPageStarted` 및 `onPageFinished` 콜백을 사용하여 로딩 상태를 업데이트합니다.
rhkrwngud445
left a comment
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.
고생하셨습니다! viewModel 컴포저블 파라미터 관련해서 코멘트 남겼는데, 이것만 확인 부탁드려요!
| onDismiss: () -> Unit, | ||
| onConsentComplete: () -> Unit, | ||
| onTermsClick: (TermType) -> Unit = {}, | ||
| viewModel: LoginViewModel, |
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.
viewModel을 인자로 받아오는 것보다 상태를 호이스팅하는 것은 어떨까요? 해당 컴포저블은 preview 작성이 어려워 보이네요!
- `PrivacyBottomSheet.kt`에서 ViewModel을 직접 참조하지 않고, `LoginScreen.kt`를 통해 상태와 이벤트를 전달받도록 수정했습니다. - `LoginScreen.kt`에서 `termsAgreementState`를 수집하고 `PrivacyBottomSheet`에 전달하도록 변경했습니다.
- PrivacyBottomSheet의 컨텐츠를 `PrivacyConsentBottomSheetContent` Composable 함수로 분리했습니다. - 분리된 `PrivacyConsentBottomSheetContent`에 대한 프리뷰를 추가했습니다. - `PrivacyConsentBottomSheet` 자체를 프리뷰로 그리면 바텀시트가 표시되지 않는 현상이 있어 Content로 따로 분리하였습니다
작업 내용
체크 안됨
전체 체크 완료
확인 방법
참고 사항
관련 이슈