[Feature/#56] 보호자 기능(역할 선택, 회원 연동, 프로필 조회)을 구현합니다.#59
Conversation
개요보호자 홈 화면 기능을 구현하고, 노인 프로필 관리 및 연결 기능을 추가합니다. 새로운 네비게이션 경로, UI 화면, 데이터 계층 작업 및 도메인 유스케이스를 도입합니다. 변경 사항
시퀀스 다이어그램sequenceDiagram
participant UI as GuardianHome Screen
participant VM as GuardianHome ViewModel
participant UC as Use Cases
participant Repo as Repository
participant Service as User Service
UI->>VM: 화면 로드
activate VM
VM->>UC: fetchUserProfileUseCase()
activate UC
UC->>Repo: fetchUserProfile()
activate Repo
Repo->>Service: API 호출
activate Service
Service-->>Repo: UserProfile
deactivate Service
Repo-->>UC: Result<UserProfile>
deactivate Repo
UC-->>VM: Result<UserProfile>
deactivate UC
par 병렬 로드
VM->>UC: fetchSeniorProfilesUseCase()
activate UC
UC->>Repo: fetchSeniorProfiles()
activate Repo
Repo->>Service: API 호출
activate Service
Service-->>Repo: List<SeniorProfileResponse>
deactivate Service
Repo-->>UC: Result<List<SeniorProfile>>
deactivate Repo
UC-->>VM: Result<List<SeniorProfile>>
deactivate UC
end
VM->>VM: uiState 업데이트
VM-->>UI: 프로필 데이터 표시
deactivate VM
sequenceDiagram
participant UI as Guardian Screen
participant VM as GuardianHome ViewModel
participant Dialog as Delete Dialog
participant UC as DeleteSenior UseCase
participant Repo as Repository
participant Service as User Service
UI->>Dialog: 삭제 버튼 클릭
Dialog->>VM: deleteProfile()
activate VM
VM->>VM: isLoading = true
VM->>UC: deleteSeniorProfileUseCase(userId)
activate UC
UC->>Repo: deleteSeniorProfile(userId)
activate Repo
Repo->>Service: DELETE API 호출
activate Service
Service-->>Repo: Result<Unit>
deactivate Service
Repo-->>UC: Result<Unit>
deactivate Repo
UC-->>VM: Result<Unit>
deactivate UC
alt 성공
VM->>UC: fetchSeniorProfilesUseCase() (새로고침)
activate UC
UC->>Repo: fetchSeniorProfiles()
activate Repo
Repo->>Service: GET API 호출
Service-->>Repo: List<SeniorProfileResponse>
deactivate Repo
UC-->>VM: Result<List<SeniorProfile>>
deactivate UC
VM->>VM: dialogState = DeleteComplete
else 실패
VM->>VM: dialogState = None
VM->>VM: 에러 로깅
end
VM->>VM: isLoading = false
VM-->>UI: 상태 업데이트
deactivate VM
sequenceDiagram
participant UI as UserConnection Screen
participant VM as UserConnection ViewModel
participant ValidateUC as ValidateParentCode UseCase
participant ConnectUC as ConnectToSenior UseCase
participant Repo as Repository
participant Service as User Service
participant Nav as Navigator
UI->>VM: OTP 입력 완료 (navigateToNext)
activate VM
VM->>ValidateUC: validateParentCodeUseCase(userCode)
activate ValidateUC
ValidateUC->>Repo: validateParentCode(userCode)
activate Repo
Repo->>Service: POST 검증 API
activate Service
Service-->>Repo: SeniorProfileResponse (id 포함)
deactivate Service
Repo-->>ValidateUC: Result<Long> (userId)
deactivate Repo
ValidateUC-->>VM: Result<Long>
deactivate ValidateUC
alt 검증 성공
VM->>Nav: navigateToConnectionCheck(userId)
VM-->>UI: ConnectionCheck 화면으로 이동
else 검증 실패
VM->>VM: errorMessage 설정
VM-->>UI: 에러 메시지 표시
end
deactivate VM
예상 코드 리뷰 노력🎯 4 (복잡) | ⏱️ ~60분 관련 가능성이 있는 PR들
시
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/role/SelectUserRoleScreen.kt (1)
72-102: 접근성 개선: 이미지 설명을 추가하세요.Line 83의
contentDescription이null로 설정되어 있어 스크린 리더 사용자가 이미지의 의미를 파악할 수 없습니다. 의미 있는 설명을 제공해주세요.🔎 제안된 수정
Image( imageVector = ImageVector.vectorResource(id = R.drawable.img_role_senior), - contentDescription = null, + contentDescription = "시니어 사용자 역할 선택", modifier = Modifier .fillMaxWidth() .size(126.dp, 112.dp) )
🧹 Nitpick comments (22)
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/model/UserConnectionUiState.kt (1)
7-8: 변경 사항 승인
errorMessage속성 추가가 올바르게 구현되었습니다. nullable String 타입은 선택적 오류 메시지를 표현하기에 적절하며, 기존 코드 패턴을 잘 따르고 있습니다.💡 선택적 개선사항: KDoc 추가
속성의 용도를 명확히 하기 위해 KDoc 주석을 추가하는 것을 고려해보세요:
data class UserConnectionUiState( val userCode: String, val userRole: UserRole?, + /** 사용자 연결 과정에서 발생한 유효성 검증 오류 메시지 */ val errorMessage: String? ) {core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/dialog/MaConfigDialog.kt (1)
47-54: 간격 조정을 고려해보세요.제목의 하단 패딩(20.dp)과 추가 Spacer(8.dp)가 중복될 수 있습니다. 일관성과 단순성을 위해 하나로 통합하는 것을 고려해보세요.
🔎 제안하는 수정
Text( text = title, color = MoaTheme.colors.black, style = MoaTheme.typography.title1Bold, - modifier = Modifier.padding(vertical = 20.dp), + modifier = Modifier.padding(top = 20.dp, bottom = 24.dp), ) - Spacer(modifier = Modifier.height(8.dp)) - MaButton(feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/role/SelectUserRoleViewModel.kt (1)
12-12: 사용하지 않는 import 정리를 고려해보세요.사이드 이펙트 메커니즘이 제거되면서
MutableSharedFlow,SharedFlow,asSharedFlowimport가 더 이상 사용되지 않는 것으로 보입니다.🔎 사용되지 않는 import 제거
import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlowAlso applies to: 14-14, 16-16
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/role/SelectUserRoleScreen.kt (3)
45-52: [선택사항] 역할 선택 콜백 통합을 고려해보세요.
onSeniorRoleClick과onGuardianRoleClick이 동일한 시그니처(UserRole) -> Unit을 가지고 있습니다. 단일onRoleClick: (UserRole) -> Unit콜백으로 통합하는 것도 고려해볼 수 있습니다.하지만 현재의 명시적인 분리 방식도 가독성 측면에서 장점이 있으므로, 이는 선택적인 개선사항입니다.
72-136: [선택사항] 역할 버튼 컴포저블 추출을 고려하세요.두 개의
MaSelectButton블록(시니어와 보호자)이 매우 유사한 구조를 가지고 있습니다. 공통 로직을 추출하여 재사용 가능한 컴포저블로 만들면 유지보수성이 향상됩니다.💡 리팩토링 예시
@Composable private fun RoleSelectionButton( onClick: () -> Unit, selected: Boolean, imageRes: Int, title: String, description: String, contentDescription: String, modifier: Modifier = Modifier ) { MaSelectButton( onClick = onClick, selected = selected, modifier = modifier.fillMaxWidth() ) { Column( modifier = Modifier.padding(vertical = 16.dp, horizontal = 20.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Image( imageVector = ImageVector.vectorResource(id = imageRes), contentDescription = contentDescription, modifier = Modifier .fillMaxWidth() .size(126.dp, 112.dp) ) Spacer(Modifier.height(18.dp)) Text( text = title, style = MoaTheme.typography.title2Semibold, modifier = Modifier.padding(bottom = 2.dp) ) Text( text = description, style = MoaTheme.typography.body1Medium, ) } } }사용 예:
RoleSelectionButton( onClick = { onSeniorRoleClick(UserRole.PARENT) }, selected = uiState.isUserRoleSenior, imageRes = R.drawable.img_role_senior, title = "저는 본인이에요", description = "인지 능력을 키우고 싶어요", contentDescription = "시니어 사용자 역할 선택" )
73-73: 역할 매핑은 문서화되어 있지만, 가독성 개선을 위해 네이밍 변경을 고려하세요.
UserRoleenum의 문서에 PARENT(시니어/치매환자), CHILD(보호자/가족)로 명시되어 있어 현재 매핑이 의도된 것으로 확인됩니다. 다만 PARENT/CHILD라는 네이밍 자체가 실제 역할의 의미와 맞지 않아 코드 가독성을 해칠 수 있습니다.도메인 요구사항이 확정되었다면,
UserRoleenum의 값을 SENIOR/GUARDIAN 또는 유사한 표현으로 변경하여 가독성을 높이는 것을 권장합니다.domain/src/main/kotlin/com/moa/app/domain/user/usecase/FetchSeniorProfilesUseCase.kt (1)
10-16: 오버로드된 invoke 메서드 사용을 고려해보세요.두 개의
invoke메서드가 오버로드되어 있어 기능적으로는 정상 작동하지만, 가독성과 의도 전달 측면에서는 명시적인 메서드 이름(예:fetchAll(),fetchById(userId: Long))을 사용하는 것이 더 명확할 수 있습니다.🔎 명시적 메서드 이름 사용 제안
class FetchSeniorProfilesUseCase @Inject constructor( private val repository: UserRepository, ) { - suspend operator fun invoke(): Result<List<SeniorProfile>> { + suspend fun fetchAll(): Result<List<SeniorProfile>> { return repository.fetchSeniorProfiles() } - suspend operator fun invoke(userId: Long): Result<SeniorProfile> { + suspend fun fetchById(userId: Long): Result<SeniorProfile> { return repository.fetchSeniorProfile(userId) } }domain/src/main/kotlin/com/moa/app/domain/user/model/SeniorProfile.kt (1)
5-11:birthDate필드의 형식 검증 강화 권장SeniorProfile 데이터 클래스 구조는 명확하고 불변성이 보장되어 있습니다.
다만
birthDate필드가 String 타입으로 되어 있으며, 현재 코드베이스 전역에서 "yyyy-MM-dd" 형식을 관례적으로 따르고 있지만 명시적인 형식 검증이나 type-safe한 처리가 부재합니다. 예를 들어 Response DTO들(ProfileResponse, SeniorProfileResponse)에@JsonFormat같은 역직렬화 시점의 검증 메커니즘이 없으며, 다른 모듈에서는 LocalDate를 사용하고 있습니다. UI 레이어에서도 날짜 형식을 변환하고 있어(예: "-"를 "."으로 치환) 형식 일관성이 관례에만 의존하고 있습니다.가능하면 Response DTO에
@JsonFormat(pattern = "yyyy-MM-dd")추가하거나, LocalDate 타입 사용을 고려하기 바랍니다.feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeScreen.kt (1)
63-64: 문자열 리소스 추출을 권장합니다.사용자에게 보이는 문자열들이 하드코딩되어 있습니다. 다국어 지원 및 유지보수를 위해
strings.xml로 추출하는 것을 권장합니다.예시:
- Line 121:
"안녕하세요, ${uiState.userName}님"→stringResource(R.string.guardian_home_greeting, uiState.userName)- Line 127:
"누구의 결과를 볼까요?"→stringResource(R.string.guardian_home_subtitle)- Line 160:
"참여자 관리"→stringResource(R.string.guardian_home_manage_participants)Also applies to: 121-121, 127-127, 160-160
domain/src/main/kotlin/com/moa/app/domain/user/usecase/ValidateParentCodeUseCase.kt (1)
9-11: 입력 값 검증 추가를 고려해 주세요.
parentCode가 빈 문자열이거나 유효하지 않은 형식일 경우, 불필요한 네트워크 요청을 방지하기 위해 use case 레벨에서 사전 검증을 추가하는 것이 좋습니다.🔎 제안된 수정 사항
suspend operator fun invoke(parentCode: String): Result<Long> { + if (parentCode.isBlank()) { + return Result.failure(IllegalArgumentException("Parent code cannot be blank")) + } return userRepository.validateParentCode(parentCode) }core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/topbar/MaHomeTopBar.kt (1)
12-12: 사용되지 않는 import
TopAppBarimport가 사용되지 않고 있습니다. 삭제를 고려해 주세요.data/src/main/kotlin/com/moa/app/data/user/model/response/SeniorProfileResponse.kt (1)
3-3: 레이어 의존성 위반: Data 레이어가 Domain 모델을 직접 참조합니다.
SeniorProfileResponse가com.moa.app.domain.auth.model.Gender를 직접 import하고 있습니다. Clean Architecture 원칙에 따르면 data 레이어는 domain 레이어에 의존하지 않아야 합니다.Data 레이어에 별도의
Gender표현(String 또는 data 레이어 전용 enum)을 사용하고, repository 구현에서 domain 모델로 매핑하는 것이 권장됩니다.🔎 제안된 수정 사항
package com.moa.app.data.user.model.response -import com.moa.app.domain.auth.model.Gender import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class SeniorProfileResponse( @SerialName("id") val id: Long, @SerialName("name") val name: String, @SerialName("birthDate") val birthDate: String, - @SerialName("gender") val gender: Gender, + @SerialName("gender") val gender: String, @SerialName("phoneNumber") val phoneNumber: String )그런 다음
UserRepositoryImpl에서 매핑 시Gender.fromString(response.gender)와 같이 변환합니다.Also applies to: 12-12
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/UserConnectionViewModel.kt (2)
87-90: 하드코딩된 "CHILD" 문자열 대신 상수 사용 권장
popUpTo에"CHILD"가 하드코딩되어 있습니다.UserRoleenum이나 실제userRole변수를 사용하는 것이 유지보수성과 타입 안전성 측면에서 좋습니다.🔎 제안된 수정 사항
private fun navigateToSeniorHome() { navigator.navigate( route = AppRoute.SeniorHome, options = NavigationOptions( - popUpTo = AppRoute.UserConnection("CHILD"), + popUpTo = AppRoute.UserConnection(UserRole.CHILD.name), inclusive = true, clearBackStack = true, ), ) }
64-76: 중복 제출 방지 및 에러 상태 초기화 고려
validateUserCode가 호출될 때 로딩 상태 체크 없이 바로 API를 호출합니다. 사용자가 버튼을 여러 번 빠르게 탭하면 중복 요청이 발생할 수 있습니다. 또한 에러 발생 후 재시도 시 이전 에러 메시지가 남아있을 수 있습니다.🔎 제안된 수정 사항
private fun validateUserCode(userCode: String) { + if (_uiState.value.isLoading) return + _uiState.update { it.copy(isLoading = true, errorMessage = null) } viewModelScope.launch { validateParentCodeUseCase(userCode).fold( - onSuccess = { userId -> navigateToConnectionCheck(userId) }, + onSuccess = { userId -> + _uiState.update { it.copy(isLoading = false) } + navigateToConnectionCheck(userId) + }, onFailure = { t -> Timber.e("validateUserCode: $t") _uiState.update { - it.copy(errorMessage = "* 유효하지 않은 회원코드예요. 다시 확인해주세요.") + it.copy( + isLoading = false, + errorMessage = "* 유효하지 않은 회원코드예요. 다시 확인해주세요." + ) } }, ) } }
UserConnectionUiState에isLoading필드 추가가 필요합니다.feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/model/ConnectionCheckUiState.kt (1)
12-13: 기본값 문자열이 사용자에게 노출될 수 있음
seniorProfile이 null일 때"name","gender"와 같은 개발용 플레이스홀더가 UI에 그대로 표시될 수 있습니다. 프로덕션 환경에서는 로컬라이즈된 기본값이나 로딩/에러 상태 처리를 고려해 주세요.feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeViewModel.kt (1)
47-49:update{}블록 내에서 현재 상태 참조 방식 개선
_uiState.value.deletable대신 람다 파라미터it을 사용하면 원자적 업데이트가 보장됩니다.🔎 수정 제안
fun updateDeletable() { - _uiState.update { it.copy(deletable = !_uiState.value.deletable) } + _uiState.update { it.copy(deletable = !it.deletable) } }data/src/main/kotlin/com/moa/app/data/user/repositoryImpl/UserRepositoryImpl.kt (1)
49-75:SeniorProfileResponse→SeniorProfile매핑 로직 중복
fetchSeniorProfile()과fetchSeniorProfiles()에서 동일한 매핑 로직이 반복됩니다. 확장 함수로 추출하여 DRY 원칙을 준수하고 유지보수성을 개선하세요.🔎 수정 제안
매핑 확장 함수를 추가하세요:
// SeniorProfileResponse를 SeniorProfile로 변환하는 확장 함수 private fun SeniorProfileResponse.toDomain() = SeniorProfile( id = id, name = name, birthDate = birthDate, gender = gender, phoneNumber = phoneNumber )그리고 기존 코드를 간소화하세요:
override suspend fun fetchSeniorProfile(userId: Long): Result<SeniorProfile> { - return userDataSource.fetchSeniorProfile(userId) - .map { - SeniorProfile( - id = it.id, - name = it.name, - birthDate = it.birthDate, - gender = it.gender, - phoneNumber = it.phoneNumber - ) - } + return userDataSource.fetchSeniorProfile(userId).map { it.toDomain() } } override suspend fun fetchSeniorProfiles(): Result<List<SeniorProfile>> { - return userDataSource.fetchSeniorProfiles() - .map { response -> - response.map { - SeniorProfile( - id = it.id, - name = it.name, - birthDate = it.birthDate, - gender = it.gender, - phoneNumber = it.phoneNumber - ) - } - } + return userDataSource.fetchSeniorProfiles().map { it.map { profile -> profile.toDomain() } } }feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/ConnectionCheckViewModel.kt (2)
41-44:isLogin필드명이 실제 용도와 불일치
isLogin이 로딩 상태 표시용으로 사용되고 있습니다. 의미적으로isLoading이 더 적절합니다.ConnectionCheckUiState에서 필드명을 변경하는 것을 고려해 주세요.
54-69: 실패 시 사용자 피드백 부재
connectToSenior()실패 시 Timber 로깅만 수행하고 사용자에게 에러를 알리지 않습니다. 토스트나 스낵바를 통해 사용자에게 실패를 알리는 것을 고려해 주세요.feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/component/SeniorProfileGrid.kt (3)
161-164: 프로필 아이콘 접근성 개선 필요성별 아이콘의
contentDescription이null로 설정되어 있습니다. 스크린 리더 사용자를 위해 성별 정보를 포함한 설명을 추가하는 것이 좋습니다.🔎 접근성 개선 제안
@Composable private fun ProfileIcon( gender: Gender, modifier: Modifier = Modifier, ) { val iconRes = if (gender == Gender.MALE) R.drawable.img_male else R.drawable.img_female + val contentDesc = if (gender == Gender.MALE) "남성" else "여성" Box( modifier = modifier .clip(RoundedCornerShape(14.dp)) .background(MoaTheme.colors.white) .padding(8.dp), contentAlignment = Alignment.Center, ) { Image( imageVector = ImageVector.vectorResource(iconRes), - contentDescription = null, + contentDescription = contentDesc, ) } }
183-187: 추가 버튼 아이콘 접근성 개선 필요추가 아이콘의
contentDescription이null로 설정되어 있습니다. 스크린 리더 사용자를 위해 적절한 설명을 추가하세요.🔎 접근성 개선 제안
Image( imageVector = ImageVector.vectorResource(id = R.drawable.ic_add_plus), - contentDescription = null, + contentDescription = "프로필 추가", colorFilter = ColorFilter.tint(MoaTheme.colors.coolGray50), )
199-202: 최신 Kotlin 문법 적용 권장
object AddButton을data object AddButton으로 변경하면 sealed interface와 함께 사용할 때 더 일관성 있고 최신 Kotlin 관례를 따릅니다.🔎 제안 사항
sealed interface SeniorItem { data class Profile(val data: SeniorProfile, val backgroundColor: Color) : SeniorItem - object AddButton : SeniorItem + data object AddButton : SeniorItem }
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (40)
app/src/main/kotlin/com/moa/app/main/MainActivity.ktcore/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/textfield/MaOtpTextField.ktcore/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/dialog/MaAlertDialog.ktcore/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/dialog/MaConfigDialog.ktcore/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/topbar/MaHomeTopBar.ktcore/designsystem/src/main/res/drawable/ic_bell.xmlcore/designsystem/src/main/res/drawable/ic_delete_circle.xmlcore/designsystem/src/main/res/drawable/img_female.xmlcore/designsystem/src/main/res/drawable/img_male.xmlcore/navigation/src/main/java/com/moa/app/navigation/AppRoute.ktdata/src/main/kotlin/com/moa/app/data/user/datasource/UserDataSource.ktdata/src/main/kotlin/com/moa/app/data/user/datasourceImpl/UserDataSourceImpl.ktdata/src/main/kotlin/com/moa/app/data/user/model/response/ProfileResponse.ktdata/src/main/kotlin/com/moa/app/data/user/model/response/SeniorProfileResponse.ktdata/src/main/kotlin/com/moa/app/data/user/repositoryImpl/UserRepositoryImpl.ktdata/src/main/kotlin/com/moa/app/data/user/service/UserService.ktdomain/src/main/kotlin/com/moa/app/domain/user/model/SeniorProfile.ktdomain/src/main/kotlin/com/moa/app/domain/user/repository/UserRepository.ktdomain/src/main/kotlin/com/moa/app/domain/user/usecase/ConnectToSeniorUseCase.ktdomain/src/main/kotlin/com/moa/app/domain/user/usecase/DeleteSeniorProfileUseCase.ktdomain/src/main/kotlin/com/moa/app/domain/user/usecase/FetchSeniorProfilesUseCase.ktdomain/src/main/kotlin/com/moa/app/domain/user/usecase/ValidateParentCodeUseCase.ktfeature/guardian/build.gradle.ktsfeature/guardian/src/main/kotlin/com/moa/app/feature/guardian/.gitkeepfeature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeScreen.ktfeature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeViewModel.ktfeature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/component/SeniorProfileGrid.ktfeature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/model/DialogState.ktfeature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/model/GuardianHomeUiState.ktfeature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/ConnectionCheckScreen.ktfeature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/ConnectionCheckViewModel.ktfeature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/UserConnectionScreen.ktfeature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/UserConnectionViewModel.ktfeature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/model/ConnectionCheckUiState.ktfeature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/model/UserConnectionUiState.ktfeature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/role/SelectUserRoleScreen.ktfeature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/role/SelectUserRoleViewModel.ktfeature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/role/model/SelectUserSideEffect.ktfeature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/splash/SplashViewModel.ktfeature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeScreen.kt
💤 Files with no reviewable changes (1)
- feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/role/model/SelectUserSideEffect.kt
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-21T09:07:31.486Z
Learnt from: wjdrjs00
Repo: team-memory-care/MoA-Android PR: 31
File: core/network/src/main/kotlin/com/moa/app/network/adapter/NetworkResultCall.kt:36-37
Timestamp: 2025-11-21T09:07:31.486Z
Learning: The MoA-Android project exclusively uses Retrofit suspend functions for network calls in the core/network module. The NetworkResultCall.execute() method intentionally throws UnsupportedOperationException because synchronous execution is not needed.
Applied to files:
data/src/main/kotlin/com/moa/app/data/user/service/UserService.kt
🧬 Code graph analysis (6)
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/dialog/MaConfigDialog.kt (1)
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/button/MaButton.kt (1)
MaButton(30-68)
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/splash/SplashViewModel.kt (1)
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/signin/SignInViewModel.kt (1)
navigateToRoute(98-106)
feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeScreen.kt (3)
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/dialog/MaAlertDialog.kt (1)
MaAlertDialog(27-120)core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/dialog/MaConfigDialog.kt (1)
MaConfigDialog(24-69)core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/topbar/MaHomeTopBar.kt (1)
MaHomeTopBar(25-64)
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/role/SelectUserRoleScreen.kt (1)
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/button/MaSelectButton.kt (1)
MaSelectButton(30-64)
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/UserConnectionViewModel.kt (5)
data/src/main/kotlin/com/moa/app/data/auth/datasource/AuthDataSource.kt (1)
setParentRole(15-15)data/src/main/kotlin/com/moa/app/data/auth/datasourceImpl/AuthDataSourceImpl.kt (1)
setParentRole(36-38)data/src/main/kotlin/com/moa/app/data/auth/service/AuthService.kt (1)
setParentRole(40-41)data/src/main/kotlin/com/moa/app/data/auth/repositoryImpl/AuthRepositoryImpl.kt (1)
setParentRole(46-51)domain/src/main/kotlin/com/moa/app/domain/auth/repository/AuthRepository.kt (1)
setParentRole(10-10)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeScreen.kt (1)
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/topbar/MaHomeTopBar.kt (1)
MaHomeTopBar(25-64)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Run Unit Tests
- GitHub Check: Run ktlint
🔇 Additional comments (37)
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/model/UserConnectionUiState.kt (1)
26-27: INIT 객체 업데이트 확인
errorMessage를null로 초기화하는 것이 적절합니다. 오류가 없는 초기 상태를 올바르게 표현하고 있습니다.feature/guardian/build.gradle.kts (1)
2-4: 컨벤션 플러그인 마이그레이션 및 의존성 구성이 적절합니다.다음 변경사항들이 모두 적절합니다:
- 컨벤션 플러그인(
moa.android.library,moa.android.compose,moa.android.hilt) 사용은 Android 프로젝트의 모범 사례입니다.- 내부 모듈 의존성(
designsystem,navigation,domain)은 Guardian 기능 모듈에 필요한 표준 구성입니다.timber는 표준 Android 로깅 라이브러리이며,kotlinx.collections.immutable은 Compose 상태 관리에 유용합니다.core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/dialog/MaAlertDialog.kt (1)
9-9: 변경 사항이 깔끔하고 하위 호환성을 유지합니다.
textAlign매개변수 추가와fillMaxWidth()적용이 올바르게 구현되었습니다. 기본값(TextAlign.Center)으로 인해 기존 동작이 유지되므로 기존 호출 지점에 영향을 주지 않습니다.
fillMaxWidth()는textAlign이 제대로 작동하기 위해 필수적이며, 타이틀과 콘텐츠 모두에 일관되게 적용되었습니다. 코드베이스 전역의 모든 사용처(7곳)가 기본값에 의존하고 있어 호환성이 완벽하게 유지됩니다.feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/role/SelectUserRoleViewModel.kt (1)
33-42: 역할별 처리 로직이 SelectUserRoleViewModel에서 UserConnectionViewModel으로 의도적으로 이관된 정상적인 리팩토링입니다.CHILD 역할 가드 제거는 의도된 변경입니다. UserConnectionViewModel의
setUserRole()메서드와UserConnectionUiState에서 역할별로 다른 동작을 처리합니다:
- PARENT 역할:
setParentRole()을 호출해 코드를 자동으로 조회하고,navigateToNext()에서 바로 SeniorHome으로 이동- CHILD 역할: 사용자가 입력한 코드 유효성을 검증(
validateUserCode())한 후 ConnectionCheck으로 이동UserConnection 화면은 역할에 따라 다른 타이틀, 버튼 텍스트, 유효성 검증 로직을 표시합니다. 이는 목적지 기반 역할 처리 패턴으로, 더 깔끔한 네비게이션 구조입니다.
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/role/SelectUserRoleScreen.kt (3)
30-43: LGTM! ViewModel 통합이 적절합니다.ViewModel의 메서드를 명시적인 콜백으로 전달하는 구조가 깔끔하고,
collectAsStateWithLifecycle을 사용하여 생명주기를 안전하게 관리하고 있습니다.
139-151: LGTM! 버튼 상태 관리가 적절합니다.
uiState.isNextEnabled로 버튼 활성화를 제어하여, 역할이 선택되지 않으면 다음 단계로 진행할 수 없도록 적절하게 구현되어 있습니다.
30-43: 부작용 처리 관련 우려는 확인되지 않습니다.현재 코드를 검토한 결과,
LaunchedEffect나Toast, 또는 생명주기 기반 부작용 처리 로직이 애초부터 존재하지 않습니다.SelectUserRoleViewModel은 깔끔한 MVVM 패턴을 따르며:
- 상태 관리는 StateFlow로 처리
- 네비게이션은 Navigator 의존성을 통해 위임
- 에러 처리가 필요 없음 (순수 역할 선택 화면)
이 아키텍처는 적절합니다. Navigator를 통한 네비게이션은 LaunchedEffect보다 더 명확하고 테스트 가능한 패턴입니다.
core/designsystem/src/main/res/drawable/ic_delete_circle.xml (1)
1-19: LGTM!아이콘이 잘 정의되어 있습니다. 빨간색 원형 배경(#FF4747)과 흰색 수평선이 삭제 아이콘으로 적절합니다.
core/designsystem/src/main/res/drawable/img_male.xml (1)
1-78: LGTM!남성 프로필 일러스트레이션이 잘 구성되어 있습니다. 피부톤, 의류, 얼굴 특징 등이 적절한 색상으로 정의되어 있으며, 눈 디테일에 스트로크 경로가 올바르게 사용되었습니다.
core/designsystem/src/main/res/drawable/img_female.xml (1)
1-87: LGTM!여성 프로필 일러스트레이션이 잘 구성되어 있습니다.
img_male.xml과 일관된 구조를 유지하면서 장식 요소(귀걸이 등)로 적절히 차별화되어 있습니다.core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/textfield/MaOtpTextField.kt (2)
49-56: OTP 입력 완료 시 중복 호출 가능성 검토 필요사용자가 마지막 숫자를 삭제 후 다시 입력하면
onComplete가 다시 호출됩니다.onComplete콜백에서 API 호출 등의 작업을 수행하는 경우, 중복 호출을 방지하기 위해 호출 측에서 디바운싱 또는 상태 체크가 필요할 수 있습니다.이 동작이 의도된 것인지 확인해 주세요.
44-45: LGTM!새로운 파라미터들이 적절한 기본값과 함께 추가되었습니다.
autoHideKeyboard = true는 좋은 UX 기본값이며,onComplete를 nullable로 처리하여 유연한 사용이 가능합니다.feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/splash/SplashViewModel.kt (1)
50-50: LGTM!
UserRole.CHILD에 대한 네비게이션이AppRoute.GuardianHome으로 올바르게 구현되었습니다. PR 목표인 보호자 기능 구현에 부합하며, 기존 네비게이션 패턴과 일관됩니다.feature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeScreen.kt (1)
42-42: LGTM!커스텀 상단 바 구현을
MaHomeTopBar디자인 시스템 컴포넌트로 교체한 것은 좋은 리팩토링입니다. UI 일관성이 향상되고 코드 중복이 줄어듭니다.Also applies to: 89-89
core/designsystem/src/main/res/drawable/ic_bell.xml (1)
1-12: LGTM!알림 벨 아이콘이 잘 정의되어 있습니다. 색상(#5A5C63)이 디자인 시스템의 다른 아이콘과 일관되며, 상단 바의 액션 아이콘으로 적합합니다.
domain/src/main/kotlin/com/moa/app/domain/user/usecase/DeleteSeniorProfileUseCase.kt (1)
6-12: 유스케이스 구현이 올바릅니다.DeleteSeniorProfileUseCase가 표준 클린 아키텍처 패턴을 따르고 있으며, 레포지토리로의 위임이 적절하게 구현되어 있습니다.
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/UserConnectionScreen.kt (2)
76-83: 에러 메시지 표시가 적절하게 구현되었습니다.에러 메시지가 null-safe하게 처리되고 있으며, 빨간색 스타일링과 적절한 간격으로 사용자에게 명확하게 표시됩니다.
108-115: 프리뷰가 상태를 올바르게 처리하고 있습니다.mutableStateOf를 사용하여 인터랙티브한 프리뷰를 구현한 것이 개발 편의성을 높여줍니다. 상태 관리가 적절하게 되어 있습니다.
domain/src/main/kotlin/com/moa/app/domain/user/usecase/ConnectToSeniorUseCase.kt (1)
6-12: 유스케이스 구현이 올바릅니다.ConnectToSeniorUseCase가 클린 아키텍처 원칙을 따르고 있으며, 레포지토리로의 단순 위임이 적절합니다.
core/navigation/src/main/java/com/moa/app/navigation/AppRoute.kt (1)
31-35: 라우트 정의가 올바르게 구현되었습니다.새로운 라우트들이 올바르게 추가되었으며, Kotlin 직렬화 어노테이션도 적절하게 적용되어 있습니다.
Also applies to: 61-62
feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeScreen.kt (3)
50-52: 빈 람다 구현을 확인해주세요.
onAlertClick,onSettingClick,onProfileClick이 빈 구현으로 되어 있습니다. PR 설명에 미완료 항목이 있다고 명시되어 있는데, 이 부분들이 의도적으로 보류된 기능인지 확인이 필요합니다.
58-81: 다이얼로그 구현이 잘 되어 있습니다.
DialogState를 사용한 다이얼로그 상태 관리가 깔끔하게 구현되어 있으며, 삭제 확인 및 완료 플로우가 적절히 처리되고 있습니다.
84-166: UI 레이아웃이 잘 구성되어 있습니다.컴포넌트 구조가 명확하고, 접근성 시맨틱(
Role.Button)도 적절히 적용되어 있습니다. 디자인 시스템 컴포넌트 활용도 좋습니다.feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/model/DialogState.kt (1)
6-11: 다이얼로그 상태 모델링이 우수합니다.Sealed interface를 활용한 타입 안전한 상태 관리가 잘 구현되어 있습니다.
@Immutable어노테이션도 Compose 최적화를 위해 적절히 사용되었습니다.data/src/main/kotlin/com/moa/app/data/user/model/response/ProfileResponse.kt (1)
6-16: 직렬화 설정이 올바르게 구성되었습니다.모든 필드에
@SerialName이 명시되어 있어 API 호환성이 보장됩니다. 깔끔한 응답 모델 구조입니다.feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/model/GuardianHomeUiState.kt (1)
7-22: UI 상태 설계가 훌륭합니다.
ImmutableList를 사용하여 Compose 재구성 최적화가 잘 되어 있으며, companion object의INIT패턴으로 안전한 기본값 제공이 구현되어 있습니다.app/src/main/kotlin/com/moa/app/main/MainActivity.kt (1)
82-83: LGTM! 새로운 라우트가 올바르게 추가되었습니다.보호자 기능을 위한
UserConnection,ConnectionCheck,GuardianHome라우트가 네비게이션 그래프에 적절히 등록되었습니다.Also applies to: 92-92
domain/src/main/kotlin/com/moa/app/domain/user/repository/UserRepository.kt (1)
9-13: LGTM! Repository 인터페이스가 적절하게 확장되었습니다.보호자-시니어 연동 기능을 위한 메서드들이 일관된 패턴(
Result래퍼, 명확한 명명 규칙)으로 추가되었습니다.data/src/main/kotlin/com/moa/app/data/user/service/UserService.kt (2)
26-27: POST 요청에서 @query 사용 확인 필요
connectToSenior가 POST 요청이지만@Query로 파라미터를 전달하고 있습니다. 일반적으로 POST 요청은@Body를 사용합니다. 백엔드 API 스펙에 맞게 구현된 것인지 확인해 주세요.
21-36: LGTM! API 엔드포인트가 올바르게 정의되었습니다.보호자-시니어 연동을 위한 API 엔드포인트들이 Retrofit 어노테이션과 함께 적절하게 구현되었습니다. 프로젝트의 suspend function 사용 패턴을 잘 따르고 있습니다. Based on learnings, MoA-Android 프로젝트는 네트워크 호출에 Retrofit suspend function을 독점적으로 사용합니다.
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/model/ConnectionCheckUiState.kt (1)
26-30: LGTM!
Gender열거형에 대한 한국어 표시명 확장 프로퍼티가 깔끔하게 구현되었습니다.feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeViewModel.kt (1)
71-89: LGTM!
loadSeniorProfiles()메서드의 에러 처리와ImmutableList변환이 적절합니다.feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/ConnectionCheckScreen.kt (2)
46-104: LGTM!화면 레이아웃과 컴포저블 구조가 잘 설계되었습니다.
MaTopAppBar, 정보 카드, 하단 버튼의 배치가 적절합니다.
19-19: 현재 import는 올바릅니다.androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel이 Hilt 공식 권장 경로입니다.androidx.hilt.navigation.compose.hiltViewModel은 해당 기능에 대해 deprecated된 경로이므로 현재 코드는 유지되어야 합니다.Likely an incorrect or invalid review comment.
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/ConnectionCheckViewModel.kt (1)
71-80: LGTM!
navigateToGuardianHome()메서드에서popUpTo와clearBackStack옵션을 사용하여 백스택을 적절히 관리하고 있습니다.data/src/main/kotlin/com/moa/app/data/user/datasource/UserDataSource.kt (1)
10-14: LGTM!새로운 시니어 프로필 관련 메서드들이 기존 인터페이스 패턴과 일관되게 추가되었습니다.
Result타입을 사용한 에러 처리 방식이 적절합니다.data/src/main/kotlin/com/moa/app/data/user/datasourceImpl/UserDataSourceImpl.kt (1)
32-42: LGTM!
fetchSeniorProfile,fetchSeniorProfiles,deleteSeniorProfile메서드들이 기존 패턴과 일관되게 구현되었습니다.
| init { | ||
| loadUserProfile() | ||
| loadSeniorProfiles() | ||
| } |
There was a problem hiding this comment.
동시 로딩 호출 시 isLoading 상태 경쟁 조건 가능성
loadUserProfile()과 loadSeniorProfiles()가 동시에 실행되면서 각각 isLoading을 독립적으로 업데이트합니다. 한쪽이 먼저 완료되면 다른 쪽이 아직 로딩 중임에도 isLoading = false로 설정될 수 있습니다. 로딩 카운터를 사용하거나 async/awaitAll로 병합하는 것을 고려해 주세요.
🤖 Prompt for AI Agents
In
feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeViewModel.kt
around lines 33-36, calling loadUserProfile() and loadSeniorProfiles()
concurrently causes a race on the shared isLoading state; change to either (a)
start both loads as async coroutines and awaitAll before setting isLoading=false
so you only clear loading after both complete, or (b) implement a loading
counter (e.g., AtomicInteger or internal counter incremented before each load
and decremented in finally, then set isLoading = counter.get() > 0) so
overlapping calls cannot prematurely clear the loading flag.
| fun deleteProfile() { | ||
| viewModelScope.launch { | ||
| val profile = (_uiState.value.dialogState as? DialogState.DeleteConfirm)?.profile ?: return@launch | ||
|
|
||
| _uiState.update { it.copy(isLoading = true) } | ||
| deleteSeniorProfileUseCase(profile.id).fold( | ||
| onSuccess = { | ||
| loadSeniorProfiles() | ||
| _uiState.update { | ||
| it.copy(isLoading = false, dialogState = DialogState.DeleteComplete) | ||
| } | ||
| }, | ||
| onFailure = { t -> | ||
| _uiState.update { it.copy(isLoading = false, dialogState = DialogState.None) } | ||
| Timber.e("deleteProfile: $t") | ||
| }, | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
삭제 성공 후 프로필 리로드와 상태 업데이트 간 경쟁 조건
loadSeniorProfiles()가 별도 코루틴으로 실행되고 바로 다음 줄에서 DialogState.DeleteComplete으로 업데이트됩니다. loadSeniorProfiles()가 완료되기 전에 다이얼로그 상태가 변경되어 일관성 없는 UI 상태가 발생할 수 있습니다. 리로드 완료 후 상태를 업데이트하거나 순차 실행을 고려해 주세요.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/component/SeniorProfileGrid.kt (2)
47-59: Remember 의존성 최적화 필요 (이전 리뷰 사항)이전 리뷰에서 지적된 사항이 아직 반영되지 않았습니다.
SeniorCardColors는 매번 새로운 리스트 인스턴스를 생성하는@Composable속성이므로, 이를remember의 의존성에 포함하면 프로필이 변경되지 않았을 때도 불필요한 재계산이 발생합니다.🔎 최적화 방안
- val cardColors = SeniorCardColors - val items = remember(profiles, cardColors) { + val items = remember(profiles) { val list = profiles.take(4).mapIndexed { index, profile -> SeniorItem.Profile( data = profile, - backgroundColor = cardColors[index % cardColors.size], + backgroundColor = SeniorCardColors[index % SeniorCardColors.size], ) }.toMutableList<SeniorItem>() if (list.size < 4) { list.add(SeniorItem.AddButton) } list }Based on learnings, 이전 리뷰 사항을 반영해 주세요.
128-141: 삭제 버튼 접근성 개선 필요 (이전 리뷰 사항)이전 리뷰에서 지적된 접근성 문제가 아직 반영되지 않았습니다. 삭제 아이콘의
contentDescription이null로 설정되어 있어 스크린 리더 사용자가 해당 버튼의 용도를 알 수 없습니다.🔎 접근성 개선 제안
if (isDeletable) { Image( imageVector = ImageVector.vectorResource(id = R.drawable.ic_delete_circle), - contentDescription = null, + contentDescription = "프로필 삭제", modifier = Modifier .align(Alignment.TopEnd)Based on learnings, 이전 리뷰 사항을 반영해 주세요.
🧹 Nitpick comments (5)
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/dialog/MaConfirmDialog.kt (1)
54-54: 간격 구성을 검토해주세요.타이틀 텍스트에 이미
padding(vertical = 20.dp)가 적용되어 하단에 20dp의 여백이 있는 상태에서, 추가로 8dp의Spacer를 두고 있습니다. 의도한 디자인이라면 문제없지만, 만약 타이틀과 버튼 사이의 총 간격이 28dp로 의도된 것이 아니라면 간격을 하나의 방식으로 통일하는 것을 고려해보세요.feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/model/ConnectionCheckUiState.kt (1)
12-13: null 폴백 값으로 영문 리터럴 대신 빈 문자열 사용을 권장합니다.
seniorProfile이나 하위 속성이 null일 때"name","gender"같은 영문 리터럴을 표시하면 사용자에게 혼란을 줄 수 있습니다. 빈 문자열("")이나 적절한 로컬라이징 처리를 고려해주세요.🔎 수정 제안
val userInfo: String - get() = "${seniorProfile?.name ?: "name"} (${seniorProfile?.gender?.koreanDisplayName ?: "gender"})" + get() = "${seniorProfile?.name ?: ""} (${seniorProfile?.gender?.koreanDisplayName ?: ""})"feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/ConnectionCheckScreen.kt (1)
49-49: UI 텍스트를 string 리소스로 분리하는 것을 권장합니다.Line 49의 빈 타이틀 문자열과 Line 56, 82, 99의 하드코딩된 한국어 텍스트들을
strings.xml로 추출하면 유지보수성과 다국어 지원이 개선됩니다.🔎 수정 제안
strings.xml에 다음 리소스 추가:<string name="connection_check_title">상대방의 정보를\n확인해 주세요.</string> <string name="connection_check_birthdate">생년월일 %s</string> <string name="button_next">다음</string>코드에서 사용:
// Line 56 text = stringResource(R.string.connection_check_title) // Line 82 text = stringResource(R.string.connection_check_birthdate, uiState.birthDate) // Line 99 text = stringResource(R.string.button_next)Line 49의 빈 타이틀이 의도적인 것인지 확인이 필요합니다.
Also applies to: 56-56, 82-82, 99-99
feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeScreen.kt (1)
50-52: 빈 람다 핸들러 구현 계획 확인 필요알림, 설정, 프로필 클릭 핸들러가 모두 빈 람다로 구현되어 있습니다. PR 설명에서 보호자 설정 및 보고서 화면이 미완료로 언급되었으므로 의도된 것으로 보이나, 향후 구현 계획을 확인해 주세요.
onAlertClick: 알림 기능 구현 예정인가요?onSettingClick: 설정 화면 네비게이션이 준비되었나요?onProfileClick: 시니어 프로필 클릭 시 상세 화면으로 이동해야 하나요?feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/component/SeniorProfileGrid.kt (1)
197-200:data object사용 권장Kotlin 1.9+에서는 sealed interface의 싱글톤 멤버에
data object를 사용할 수 있습니다. 이는 더 나은toString()표현과 구조 분해를 제공합니다.🔎 개선 제안
sealed interface SeniorItem { data class Profile(val data: SeniorProfile, val backgroundColor: Color) : SeniorItem - object AddButton : SeniorItem + data object AddButton : SeniorItem }
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/dialog/MaConfirmDialog.ktfeature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeScreen.ktfeature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeViewModel.ktfeature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/component/SeniorProfileGrid.ktfeature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/ConnectionCheckScreen.ktfeature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/model/ConnectionCheckUiState.kt
🚧 Files skipped from review as they are similar to previous changes (1)
- feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeViewModel.kt
🧰 Additional context used
🧬 Code graph analysis (2)
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/dialog/MaConfirmDialog.kt (1)
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/button/MaButton.kt (1)
MaButton(30-68)
feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeScreen.kt (4)
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/dialog/MaAlertDialog.kt (1)
MaAlertDialog(27-120)core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/dialog/MaConfirmDialog.kt (1)
MaConfirmDialog(24-69)core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/topbar/MaHomeTopBar.kt (1)
MaHomeTopBar(25-64)feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/component/SeniorProfileGrid.kt (1)
SeniorProfileGrid(38-94)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run Unit Tests
🔇 Additional comments (6)
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/dialog/MaConfirmDialog.kt (3)
24-33: 함수 시그니처가 적절합니다.Composable 컨벤션을 잘 따르고 있으며, 필수 파라미터와 선택적 파라미터가 적절히 배치되어 있습니다.
DialogProperties를 커스터마이징할 수 있도록 노출한 점도 좋습니다.
34-52: 다이얼로그 레이아웃과 타이틀 구현이 깔끔합니다.
BasicAlertDialog를 사용한 구조가 적절하며, 테마 시스템을 잘 활용하고 있습니다. 코너 반경과 패딩도 디자인 시스템과 일관성 있게 적용되었습니다.
71-80: 프리뷰 함수가 잘 구현되었습니다.실제 사용 사례를 잘 보여주는 프리뷰로, 컴포넌트의 동작을 개발 중에 쉽게 확인할 수 있습니다.
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/ConnectionCheckScreen.kt (1)
26-115: 전체 구조가 Compose 모범 사례를 잘 따르고 있습니다.stateful/stateless composable 분리, lifecycle-aware 상태 수집, Preview 제공 등 Compose 권장 패턴이 잘 적용되었습니다.
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/connection/model/ConnectionCheckUiState.kt (1)
26-30: 이 코드는 문제가 없습니다.
Genderenum은MALE,FEMALE두 가지 값만 정의되어 있으며,when표현식에서 모든 경우가 처리되어 있습니다. Kotlin의when은 enum 타입에 대해 컴파일 타임에 완전성(exhaustiveness)을 검사하므로, 모든 케이스가 처리되면else분기 없이도 안전합니다.Likely an incorrect or invalid review comment.
feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeScreen.kt (1)
58-81: 다이얼로그 상태 관리 구현 우수
DialogState를 사용한 다이얼로그 처리가 적절하게 구현되었습니다. 삭제 확인 및 완료 플로우가 명확하게 분리되어 있고, 콜백 처리도 올바릅니다.
Related issue 🛠
Work Description ✏️
Screenshot 📸
Uncompleted Tasks 😅
Summary by CodeRabbit
릴리스 노트
새로운 기능
UI/UX 개선
✏️ Tip: You can customize this high-level summary in your review settings.