-
Notifications
You must be signed in to change notification settings - Fork 4
[UI] 로그인 화면 ui 구현 #16
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
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAuth 플로우 전면 확장: 네비게이션에 스플래시와 단계별 인증/가입 경로 추가, 관련 스크린/공통 컴포넌트 신규 도입, 문자열 리소스 다수 추가. 빌드에 Material/Icons 의존성 포함. 기존 LoginScreen(구 경로) 제거, ViewModel 패키지 이동 및 DI 임포트 갱신, NavGraph의 Auth 시작 지점이 Splash로 변경. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant NavHost as NavHost (Auth Graph)
participant Splash as SplashScreen
participant Login as LoginScreen
participant Signup as SignupEmailInput
participant Verify as EmailVerification
participant Pwd as PasswordInput
participant Nick as NicknameInput
participant Club as ClubCodeInput
participant Home as Home
User->>NavHost: 앱 시작
NavHost->>Splash: startDestination = Splash
Splash-->>NavHost: onNavigateToLogin()
NavHost->>Login: navigate(Route.Login)
alt 로그인 성공
Login-->>NavHost: onNavigateToHome()
NavHost->>Home: navigate(Route.Home) and popUpTo(AuthGraph) { inclusive=true }
else 비밀번호 찾기
Login-->>NavHost: onNavigateToFindPassword()
NavHost->>FindPwd: navigate(Route.FindPassword)
FindPwd-->>NavHost: onPasswordResetComplete()
NavHost->>Login: navigateUp()/navigate(Login)
else 회원가입 플로우
Login-->>NavHost: onNavigateToSignup()
NavHost->>Signup: navigate(Route.Signup)
Signup-->>NavHost: onNavigateToEmailVerification(email)
NavHost->>Verify: navigate(Route.EmailVerification)
Verify-->>NavHost: onVerificationComplete(code)
NavHost->>Pwd: navigate(Route.PasswordInput)
Pwd-->>NavHost: onPasswordComplete(pw, confirm)
NavHost->>Nick: navigate(Route.NicknameInput)
Nick-->>NavHost: onNavigateToClubCode(nickname)
NavHost->>Club: navigate(Route.ClubCodeInput)
Club-->>NavHost: onNavigateToHome(clubName)
NavHost->>Home: navigate(Route.Home) and popUpTo(AuthGraph) { inclusive=true }
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements the login and authentication UI flow for the WhosIn application, including multiple screens for user registration and authentication. The implementation includes splash screen, login screen, signup flow with email verification, password input, nickname selection, and club code verification screens.
- Implements comprehensive authentication UI flow with 7 different screens
- Creates reusable login components for buttons, input fields, and number inputs
- Adds navigation structure and routing for the complete signup/login process
Reviewed Changes
Copilot reviewed 21 out of 24 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| gradle/libs.versions.toml | Adds material icons extended dependency |
| composeApp/build.gradle.kts | Integrates material icons and material design dependencies |
| LoginViewModel.kt | Fixes package structure for login view model |
| Component files | Creates reusable UI components for login flow |
| Screen files | Implements all authentication screens (splash, login, signup, etc.) |
| Navigation files | Updates routing and navigation graph for auth flow |
| strings.xml | Adds localized strings for all authentication screens |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| val isMaxLengthReached = maxLength != null && value.length >= maxLength | ||
| OutlinedTextField( | ||
| value = value, | ||
| onValueChange = onValueChange, |
Copilot
AI
Sep 23, 2025
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.
The isMaxLengthReached variable is calculated but never used in the component logic. Consider removing it or implementing the intended max length validation functionality.
| onValueChange = onValueChange, | |
| onValueChange = { newValue -> | |
| if (maxLength == null || newValue.length <= maxLength) { | |
| onValueChange(newValue) | |
| } | |
| }, |
| .fillMaxWidth(), | ||
| shape = RoundedCornerShape(8.dp), | ||
| colors = OutlinedTextFieldDefaults.colors( | ||
| focusedBorderColor = if (isMaxLengthReached) Color(0xFFE5E5E5) else Color(0xFFF89531), |
Copilot
AI
Sep 23, 2025
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.
The isMaxLengthReached variable is used here but was never properly implemented in the onValueChange logic. Text input is not actually limited by maxLength, making this condition ineffective.
| val isMaxLengthReached = maxLength != null && value.length >= maxLength | ||
| OutlinedTextField( | ||
| value = value, | ||
| onValueChange = onValueChange, |
Copilot
AI
Sep 23, 2025
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.
The onValueChange callback should enforce maxLength limit when provided. Currently, users can input unlimited text despite the maxLength parameter. Add validation: onValueChange = { if (maxLength == null || it.length <= maxLength) onValueChange(it) }
| onValueChange = onValueChange, | |
| onValueChange = { | |
| if (maxLength == null || it.length <= maxLength) { | |
| onValueChange(it) | |
| } | |
| }, |
| // 8자 제한 | ||
| if (newValue.length <= 8) { | ||
| nickname = newValue | ||
| } |
Copilot
AI
Sep 23, 2025
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.
The 8-character limit is implemented manually here but the CommonLoginInputField component also has a maxLength parameter. Use the component's maxLength feature instead: remove this manual validation and rely on the component's built-in functionality.
| // 8자 제한 | |
| if (newValue.length <= 8) { | |
| nickname = newValue | |
| } | |
| nickname = newValue |
| painter = painterResource(Res.drawable.ic_back), | ||
| contentDescription = stringResource(Res.string.back_button), | ||
| tint = Color.Black, | ||
| modifier = Modifier.size(14.dp) |
Copilot
AI
Sep 23, 2025
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.
Icon size inconsistency detected. Other screens use 18.dp for the back icon size (SignupEmailInputScreen.kt:67, NicknameInputScreen.kt:67). Consider using consistent sizing across all screens.
| modifier = Modifier.size(14.dp) | |
| modifier = Modifier.size(18.dp) |
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.
Actionable comments posted: 2
🧹 Nitpick comments (29)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SplashScreen.kt (2)
37-41: 로고 이미지 a11y: 장식이면 contentDescription을 null로장식 이미지는 스크린리더가 읽지 않도록 null이 적절합니다. 텍스트가 필요하면 stringResource로 지역화해주세요.
- Image( - painter = painterResource(Res.drawable.img_logo_white), - contentDescription = "logo", - modifier = Modifier.size(width = 160.dp, height = 122.dp) - ) + Image( + painter = painterResource(Res.drawable.img_logo_white), + contentDescription = null, + modifier = Modifier.size(width = 160.dp, height = 122.dp) + )
20-24: 테스트/프리뷰 친화성: 지연시간을 파라미터로 주입지연시간 하드코딩 대신 파라미터로 받아 Preview/테스트에서 0으로 줄일 수 있게 하면 UX/개발 편의가 좋아집니다.
-fun SplashScreen( +fun SplashScreen( modifier: Modifier = Modifier, - onNavigateToLogin: () -> Unit = {} + onNavigateToLogin: () -> Unit = {}, + splashDelayMillis: Long = 2000 ) { - LaunchedEffect(Unit) { - delay(2000) + LaunchedEffect(Unit) { + delay(splashDelayMillis) onNavigateToLogin() }Also applies to: 26-29
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginButton.kt (3)
33-38: 비활성 색 대비(Contrast) 개선 권장회색 배경(0xD2D2D2) + 흰 텍스트는 대비가 낮습니다. 기본 disabled 색상을 사용하거나 대비를 높여주세요.
colors = ButtonDefaults.buttonColors( containerColor = backgroundColor, contentColor = textColor, - disabledContainerColor = Color(0xFFD2D2D2), - disabledContentColor = Color.White + // 기본 disabled 색상 사용으로 대비 확보 ),
41-45: 타이포그래피는 Theme를 통해 일관되게MaterialTheme.typography를 사용하면 전역 테마 변경에 자연스럽게 따라갑니다.
- Text( - text = text, - fontSize = 16.sp, - fontWeight = FontWeight.W600 - ) + Text( + text = text, + style = androidx.compose.material3.MaterialTheme.typography.titleMedium.copy( + fontWeight = FontWeight.W600 + ) + )
63-69: Preview도 Theme로 감싸기Disabled 프리뷰도 테마 적용으로 실제 렌더와 일치시키는 것을 권장합니다.
@Preview @Composable fun CommonLoginButtonDisabledPreview() { - CommonLoginButton( - text = "비활성화된 버튼", - onClick = {}, - enabled = false - ) + WhosInTheme { + CommonLoginButton( + text = "비활성화된 버튼", + onClick = {}, + enabled = false + ) + } }composeApp/src/commonMain/composeResources/values/strings.xml (1)
4-5: 라벨 언어/톤 일관성영문/한글 혼용으로 어색합니다. 아래처럼 통일 권장합니다.
- <string name="email_label">E-mail</string> - <string name="password_label">password</string> + <string name="email_label">이메일</string> + <string name="password_label">비밀번호</string>composeApp/build.gradle.kts (1)
60-60: 불필요한 compose.material 의존성 정리 검토M3만 사용하는 경우 compose.material은 생략 가능입니다. 사용처 없으면 제거해 경량화하세요.
- implementation(compose.material)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SignupEmailInputScreen.kt (4)
56-61: 터치 타겟 a11y: IconButton 크기 축소 지양IconButton을 24dp로 줄이면 권장 최소 터치 영역(48dp)을 위반합니다. Icon 크기만 줄이고 버튼은 기본 크기를 유지하세요.
- IconButton( - onClick = onNavigateBack, - modifier = Modifier - .padding(bottom = 32.dp) - .size(24.dp) - ) { + IconButton( + onClick = onNavigateBack, + modifier = Modifier + .padding(bottom = 32.dp) + ) { Icon( painter = painterResource(Res.drawable.ic_back), contentDescription = stringResource(Res.string.back_button), tint = Color.Black, - modifier = Modifier - .size(18.dp) + modifier = Modifier.size(24.dp) ) }Also applies to: 63-68
86-94: IME에 가려지는 하단 버튼 대응imePadding을 추가하고, 이메일은 trim 후 전달하는 것이 안전합니다.
- CommonLoginButton( + CommonLoginButton( text = stringResource(Res.string.next_button), - onClick = { onNavigateToEmailVerification(email) }, + onClick = { onNavigateToEmailVerification(email.trim()) }, enabled = email.isNotBlank(), modifier = Modifier .align(Alignment.BottomCenter) .padding(horizontal = 16.dp) - .padding(bottom = 52.dp) + .padding(bottom = 52.dp) + .imePadding() )
42-42: 입력 값 보존성을 위해 rememberSaveable 고려프로세스/구성 변경 시 값 유지가 필요하면 rememberSaveable이 유용합니다.
- var email by remember { mutableStateOf("") } + var email by androidx.compose.runtime.saveable.rememberSaveable { mutableStateOf("") }
79-83: 이메일 입력 UX: 키보드 타입/IME 액션 지정email 키보드/Next 액션을 지정하면 UX가 좋아집니다. 공용 입력 컴포넌트에 키보드 옵션을 전달 가능하게 확장하는 것을 권장합니다.
아래처럼 CommonLoginInputField에 옵션을 추가(외부 파일 변경)한 뒤 본 호출부에 전달하세요.
변경(외부 파일: CommonLoginInputField):
@Composable fun CommonLoginInputField( modifier: Modifier = Modifier, value: String, onValueChange: (String) -> Unit, placeholder: String, isPassword: Boolean = false, maxLength: Int? = null, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default ) { OutlinedTextField( value = value, onValueChange = onValueChange, ... keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, singleLine = true ) }본 파일 적용:
CommonLoginInputField( value = email, onValueChange = { email = it }, - placeholder = stringResource(Res.string.email_placeholder) + placeholder = stringResource(Res.string.email_placeholder), + keyboardOptions = androidx.compose.ui.text.input.KeyboardOptions( + keyboardType = androidx.compose.ui.text.input.KeyboardType.Email, + imeAction = androidx.compose.ui.text.input.ImeAction.Next + ) )composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt (2)
41-44: maxLength 전달 시 입력 길이도 제한되도록 처리 권장현재는 테두리 색만 바뀌고 실제 입력은 제한되지 않습니다. 아래처럼 onValueChange에서 컷팅해 주세요.
- onValueChange = onValueChange, + onValueChange = { input -> + val limited = if (maxLength != null) input.take(maxLength) else input + onValueChange(limited) + },
75-79: 아이콘 contentDescription 문자열 리소스로 이동 권장하드코딩된 한글 대신 stringResource 사용을 권장합니다(접근성/i18n).
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt (1)
48-49: 버튼 활성 조건에 “두 비밀번호 일치” 추가사용자 실수를 줄이기 위해 동일성 체크를 포함하세요.
- val isComplete = password.isNotBlank() && confirmPassword.isNotBlank() + val isComplete = password.isNotBlank() && confirmPassword.isNotBlank() && password == confirmPasswordcomposeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt (1)
87-97: 중복 길이 제한 로직 정리maxLength로 컴포넌트에서 컷팅하도록 하면 화면단 onValueChange 검사는 제거 가능합니다.
- CommonLoginInputField( - value = nickname, - onValueChange = { newValue -> - // 8자 제한 - if (newValue.length <= 8) { - nickname = newValue - } - }, - placeholder = stringResource(Res.string.nickname_input_placeholder), - maxLength = 8 - ) + CommonLoginInputField( + value = nickname, + onValueChange = { nickname = it }, + placeholder = stringResource(Res.string.nickname_input_placeholder), + maxLength = 8 + )composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/LoginScreen.kt (3)
50-52: 로그인 버튼 활성 조건 추가빈 값 로그인 방지를 위해 간단한 활성 조건을 둡니다.
var email by remember { mutableStateOf("") } var password by remember { mutableStateOf("") } + val isLoginEnabled = email.isNotBlank() && password.isNotBlank()
109-113: 로그인 버튼 비활성화 처리 연결위에서 정의한 활성 조건을 버튼에 반영하세요.
CommonLoginButton( text = stringResource(Res.string.login_button), - onClick = onNavigateToHome, - modifier = Modifier.padding(bottom = 12.dp) + onClick = onNavigateToHome, + enabled = isLoginEnabled, + modifier = Modifier.padding(bottom = 12.dp) )
65-69: 장식용 이미지 접근성 처리로고는 장식용이면 contentDescription = null 권장.
Image( painter = painterResource(Res.drawable.img_logo_orange), - contentDescription = "logo", + contentDescription = null, modifier = Modifier.size(width = 160.dp, height = 122.dp) )composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/FindPasswordScreen.kt (2)
42-43: 간단한 이메일 유효성 체크 추가 제안오타 제출 방지를 위해 기본 형식 검증을 추가하세요.
var email by remember { mutableStateOf("") } + val isValidEmail = Regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$").matches(email.trim())
86-95: 버튼 활성 조건에 이메일 형식 반영형식 불일치 시 비활성화.
CommonLoginButton( text = stringResource(Res.string.send_email_button), onClick = { onPasswordResetComplete(email) }, - enabled = email.isNotBlank(), + enabled = isValidEmail, modifier = Modifier .align(Alignment.BottomCenter) .padding(horizontal = 16.dp) .padding(bottom = 52.dp) )composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/EmailVerificationScreen.kt (2)
129-136: 백스페이스 시 이전 자리 값도 삭제되도록 UX 개선포커스만 이동하면 사용자가 한 번 더 지워야 합니다. 이전 자리 값을 지워주세요.
onBackspace = { // 빈 박스에서 백스페이스 시 이전 박스로 이동 if (index > 0) { val prevIndex = index - 1 + val newCode = verificationCode.copyOf() + newCode[prevIndex] = "" + verificationCode = newCode currentFocusIndex = prevIndex focusRequesters[prevIndex].requestFocus() } },
114-128: 인증코드 영문/숫자 허용 여부 확인 필요현재 숫자만 허용합니다. Figma 코멘트대로 영문/숫자 혼합이 요구된다면 NumberInputBox의 필터/keyboardType을 파라미터화해 반영하겠습니다.
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt (1)
160-166: 백스페이스 UX 개선이전 칸으로 이동 시 해당 자리도 비워주세요.
onBackspace = { if (index > 0) { val prevIndex = index - 1 + val newCode = clubCode.copyOf() + newCode[prevIndex] = "" + clubCode = newCode currentFocusIndex = prevIndex focusRequesters[prevIndex].requestFocus() } },composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/WhosInNavGraph.kt (2)
47-56: 로그인 후 Auth 스택 제거 제안뒤로가기 시 로그인으로 돌아가지 않도록 AuthGraph를 popUpTo로 정리하는 게 자연스럽습니다.
- onNavigateToHome = { - navController.navigate(Route.Home) - }, + onNavigateToHome = { + navController.navigate(Route.Home) { + popUpTo(Route.AuthGraph) { inclusive = true } + } + },
81-90: 불필요한 파라미터 제거로 경고 감소사용하지 않는 backStackEntry는 제거 가능합니다.
- composable<Route.EmailVerification> { backStackEntry -> + composable<Route.EmailVerification> { EmailVerificationScreen(composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/NumberInputBox.kt (4)
71-81: 백스페이스 처리와 숫자 필터 로직을 조금 더 견고하게 다듬기 제안
- ZWSP 제거 후 비어 있는 경우를 우선 처리하면 “문자 삭제”와 “이전 칸으로 이동”을 더 일관되게 다룰 수 있습니다.
- 서버/백엔드가 ASCII 숫자만 기대한다면
isDigit()대신'0'..'9'로 한정하는 편이 안전합니다.isDigit()은 유니코드 숫자(예: 아라비아 숫자)도 통과시킵니다.아래처럼 정리하면 동작이 선명해집니다.
- onValueChange = { newValue -> - val cleaned = newValue.text.replace("\u200B", "") - val filtered = cleaned.filter { it.isDigit() }.take(1) - - if (newValue.text.isEmpty() && value.isEmpty()) { - onBackspace?.invoke() - return@BasicTextField - } - - onValueChange(filtered) - }, + onValueChange = { newValue -> + val cleaned = newValue.text.replace("\u200B", "") + // 비어 있으면 먼저 처리 (삭제/이동) + if (cleaned.isEmpty()) { + if (value.isEmpty()) { + onBackspace?.invoke() // 이미 비어있던 상태에서의 백스페이스 → 이전 칸으로 + } else { + onValueChange("") // 내용이 있던 칸을 비우는 첫 번째 백스페이스 + } + return@BasicTextField + } + // ASCII 숫자만 허용 + val filtered = cleaned.filter { it in '0'..'9' }.take(1) + onValueChange(filtered) + },
86-87: IME 액션(Next)과 키보드 액션 연동(선택사항)OTP/인증코드 UX에선 Next 버튼이 있는 편이 낫습니다. 외부에서 다음 포커스로 이동시키도록 콜백을 붙일 수 있게 해두면 좋아요.
- keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Next + ), + keyboardActions = KeyboardActions( + onNext = { /* TODO: 외부 콜백/FocusRequester로 다음 칸 포커스 이동 */ } + ),추가 import:
import androidx.compose.ui.text.input.ImeAction import androidx.compose.foundation.text.KeyboardActions
60-67: 포커스 표시 로컬 상태 지원 고려(선택사항)현재는 isFocused를 외부에서 넘겨줘야 테두리 색이 바뀝니다. 내부에서 focusChanged 이벤트를 로컬 상태로도 잡아
val drawFocused = isFocused || localFocused처럼 처리하면, 부모가 별도 상태를 들고 있지 않아도 기본 포커스 하이라이트가 동작해 사용성이 좋아집니다.
69-99: 접근성(스크린리더) 힌트 추가 제안(선택사항)단일 자리 입력 용도임을 스크린리더가 알 수 있도록 semantics 라벨을 부여하면 접근성이 좋아집니다.
- modifier = Modifier.onFocusChanged { focusState -> - onFocusChanged?.invoke(focusState.isFocused) - }, + modifier = Modifier + .onFocusChanged { focusState -> + onFocusChanged?.invoke(focusState.isFocused) + } + .semantics { contentDescription = "인증코드 숫자 한 자리 입력" },추가 import:
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.contentDescription
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
composeApp/src/commonMain/composeResources/drawable/ic_back.pngis excluded by!**/*.pngcomposeApp/src/commonMain/composeResources/drawable/img_logo_orange.pngis excluded by!**/*.pngcomposeApp/src/commonMain/composeResources/drawable/img_logo_white.pngis excluded by!**/*.png
📒 Files selected for processing (21)
composeApp/build.gradle.kts(2 hunks)composeApp/src/commonMain/composeResources/values/strings.xml(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/App.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/Route.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/WhosInNavGraph.kt(2 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/LoginScreen.kt(0 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeState.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/EmailVerificationScreen.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/FindPasswordScreen.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/LoginScreen.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SignupEmailInputScreen.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SplashScreen.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginButton.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/NumberInputBox.kt(1 hunks)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/viewmodel/LoginViewModel.kt(1 hunks)gradle/libs.versions.toml(2 hunks)
💤 Files with no reviewable changes (1)
- composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/LoginScreen.kt
🧰 Additional context used
🧬 Code graph analysis (12)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt (2)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt (1)
CommonLoginInputField(30-85)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginButton.kt (1)
CommonLoginButton(18-47)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SplashScreen.kt (1)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/LoginScreen.kt (5)
{}(41-41)LoginScreenPreview(36-43)LoginScreen(13-34)Text(23-32)Column(20-33)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt (2)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt (1)
CommonLoginInputField(30-85)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginButton.kt (1)
CommonLoginButton(18-47)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/FindPasswordScreen.kt (2)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt (1)
CommonLoginInputField(30-85)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginButton.kt (1)
CommonLoginButton(18-47)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginButton.kt (1)
composeApp/src/commonMain/kotlin/ui/theme/Theme.kt (1)
WhosInTheme(31-43)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SignupEmailInputScreen.kt (2)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt (1)
CommonLoginInputField(30-85)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginButton.kt (1)
CommonLoginButton(18-47)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt (1)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/LoginScreen.kt (5)
{}(41-41)LoginScreenPreview(36-43)LoginScreen(13-34)Column(20-33)Text(23-32)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt (2)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/NumberInputBox.kt (1)
NumberInputBox(31-101)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginButton.kt (1)
CommonLoginButton(18-47)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/LoginScreen.kt (3)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginInputField.kt (1)
CommonLoginInputField(30-85)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginButton.kt (1)
CommonLoginButton(18-47)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/LoginScreen.kt (3)
LoginScreen(13-34)Text(23-32)Column(20-33)
composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/WhosInNavGraph.kt (8)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SplashScreen.kt (1)
SplashScreen(20-43)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/LoginScreen.kt (1)
LoginScreen(43-150)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/FindPasswordScreen.kt (1)
FindPasswordScreen(36-96)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/SignupEmailInputScreen.kt (1)
SignupScreen(36-96)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/EmailVerificationScreen.kt (1)
EmailVerificationScreen(43-162)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/PasswordInputScreen.kt (1)
PasswordInputScreen(39-133)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/NicknameInputScreen.kt (1)
NicknameInputScreen(37-111)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeInputScreen.kt (1)
ClubCodeInputScreen(51-268)
composeApp/src/commonMain/kotlin/org/whosin/client/App.kt (1)
composeApp/src/androidMain/kotlin/org/whosin/client/MainActivity.kt (2)
AppAndroidPreview(21-25)onCreate(10-19)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/EmailVerificationScreen.kt (2)
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/NumberInputBox.kt (1)
NumberInputBox(31-101)composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/CommonLoginButton.kt (1)
CommonLoginButton(18-47)
🔇 Additional comments (7)
composeApp/src/commonMain/kotlin/org/whosin/client/App.kt (1)
5-5: import 정리 잘 하셨네요! 👍wildcard import(
*)를 명시적 import로 변경하고 사용하지 않는 import들을 제거한 것이 좋습니다. 코드가 더 명확해지고 빌드 성능에도 도움이 됩니다.composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt (1)
14-14: DI 모듈 import 경로 업데이트 완료.LoginViewModel의 새로운 패키지 경로에 맞춰 import 구문이 올바르게 업데이트되었습니다. 패키지 구조 변경에 따른 필수적인 수정사항이네요.
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/viewmodel/LoginViewModel.kt (1)
1-1: 패키지 변경 일관성 확인 — 문제 없음LoginViewModel이 composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/viewmodel/LoginViewModel.kt로 이동했고, composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt의 import 및 viewModelOf(::LoginViewModel)도 업데이트되어 이전 FQCN 잔재가 없습니다.
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/clubcode/ClubCodeState.kt (1)
3-7: LGTM상태 모델 단순·명확합니다. 이후 UI state 호이스팅에도 적합해 보여요.
composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/Route.kt (1)
10-33: LGTMSealed Route 확장은 명확하고 직렬화 태깅도 적절합니다.
composeApp/src/commonMain/kotlin/org/whosin/client/presentation/auth/login/component/NumberInputBox.kt (2)
31-42: 구성/의도 매우 깔끔합니다. API도 재사용성 높아요.단일 문자 입력·포커스 테두리 처리·콜백 분리까지 전반 설계가 좋습니다. 이 상태로도 충분히 머지 가능한 퀄리티입니다.
디자인/기획 확인: 현재 컴포넌트는 숫자만 허용합니다. PR 본문에 “인증코드가 영숫자일 수 있음”이 언급돼 있는데, 실제 요구사항이 영숫자라면 필터 정책을 확정해 주세요(숫자만 vs 영숫자). 필요 시 확장 가능한 형태로 바꿔드릴 수 있어요.
45-51: ZWSP로 레이아웃/커서 유지하는 접근 OK. iOS/Android 동작만 한 번 확인 부탁ZWSP → ""로의 변경 이벤트에 의존해 백스페이스 콜백을 트리거하는 패턴은 플랫폼별 IME 구현 차이를 조금 탈 수 있습니다. 실제 단말에서 “빈 상태에서 백스페이스 시 onBackspace가 안정적으로 호출되는지”만 확인해 주세요.
gradle/libs.versions.toml
Outdated
| ktor = "3.2.3" | ||
| kotlinx-serialization = "1.9.0" | ||
| koin = "4.1.0" | ||
| materialIconsExtended = "1.7.8" |
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.
버전 카탈로그에서 AndroidX icons 항목 제거
Compose MPP에 맞춰 JetBrains compose.materialIconsExtended를 사용하므로 해당 버전/라이브러리 항목은 제거하세요.
-materialIconsExtended = "1.7.8"-androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "materialIconsExtended" }Also applies to: 53-53
🤖 Prompt for AI Agents
In gradle/libs.versions.toml at lines 21 (and also at line 53), remove the
AndroidX materialIconsExtended version entry (materialIconsExtended = "1.7.8")
because Compose MPP uses JetBrains compose.materialIconsExtended; delete these
entries from the version catalog and ensure any build files that referenced the
removed catalog key are updated to depend on the JetBrains
compose.materialIconsExtended coordinate instead (or rely on the compose
plugin’s bundled icons), so no dangling references remain.
🚀 이슈번호
✏️ 변경사항
📷 스크린샷
https://github.com/user-attachments/assets/17e913de-ca3d-455a-b8c9-7f9022adcb28
✍️ 사용법
🎸 기타
Summary by CodeRabbit