Skip to content

[FEAT] 로그인 기능 구현#15

Merged
Sangyoon98 merged 5 commits into
devfrom
SPM-20
Oct 16, 2025
Merged

[FEAT] 로그인 기능 구현#15
Sangyoon98 merged 5 commits into
devfrom
SPM-20

Conversation

@Sangyoon98
Copy link
Copy Markdown
Member

@Sangyoon98 Sangyoon98 commented Oct 15, 2025

📝 Summary

로그인 기능 구현

🙏 Question & PR point

📬 Reference

Summary by CodeRabbit

  • New Features

    • 회원가입 흐름 추가 및 로그인→회원가입 네비게이션 연결.
    • 회원가입 API·유스케이스·폼 로직 추가로 가입·인증 토큰 발급 지원.
    • 입력 검증 유효성 검사 제공(이메일/비밀번호 등).
  • UI/UX

    • 로그인·회원가입 화면 전면 개편(로고, 입력폼, 로딩, 포커스 해제, 뒤로가기).
    • 상단 토스트형 스낵바 도입으로 오류/액션 통합 노출.
    • 버튼·입력 필드 슬롯화, 새로운 컬러 팔레트·타이포그래피·아이콘 및 로고 추가.
  • Chores

    • 네트워크 설정·로깅·JSON 처리 개선 및 인증 저장소를 DataStore로 마이그레이션.

@Sangyoon98 Sangyoon98 self-assigned this Oct 15, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 15, 2025

Walkthrough

로그인·회원가입 흐름이 토큰 기반으로 재설계되고 회원가입 화면·뷰모델·유스케이스가 추가되었습니다. 네트워크(베이스 URL, Gson, OkHttp 로깅) 및 서버 에러 파싱 유틸이 도입되었고, 공통 UI(버튼/텍스트필드/스낵바), 테마(컬러·타입) 및 리소스가 대대적으로 갱신되었습니다.

Changes

Cohort / File(s) Change Summary
Navigation
app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt
ROUTE_SIGNUP 추가, LoginScreenSignUpScreen 네비게이션 연결(성공 시 홈으로 이동하며 백스택 정리)
Network Core
app/src/main/java/com/sampoom/android/core/network/ErrorHandling.kt, .../NetworkModule.kt
ApiErrorResponse·Throwable.serverMessageOrNull() 추가. Retrofit baseUrl → https://sampoom.store/api/, Gson 네이밍 정책 적용, OkHttp 로깅 인터셉터 및 타임아웃 설정
UI Components
app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt, .../CommonTextField.kt, .../CommonSnackBar.kt
CommonButton API를 content 슬롯으로 변경. CommonTextField에 isError/errorMessage 추가. TopSnackBarHost 및 ShowErrorSnackBar 도입
Theme / Typography
app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt, .../Theme.kt, .../Type.kt
컬러 팔레트(Main 계열·상태 색상 등) 및 다크/라이트 헬퍼 추가. GmarketSansFamily 적용 및 Typography 교체
Auth DTO/API
app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt, .../dto/LoginResponseDto.kt, .../dto/SignUpRequestDto.kt, .../dto/SignUpResponseDto.kt, (삭제).../dto/UserDto.kt
로그인 응답을 ApiResponse<LoginResponseDto>로 변경, 엔드포인트 경로 변경(login,signup), 회원가입 DTO/응답 추가, 기존 UserDto 삭제
Auth Data Layer
app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt, .../data/mapper/AuthMappers.kt, .../data/local/preferences/AuthPreferences.kt
signUp 구현(가입 후 자동 로그인). 매퍼 업데이트, 토큰을 access/refresh로 저장하도록 변경. SharedPreferences → DataStore 마이그레이션
Auth Domain
app/src/main/java/com/sampoom/android/feature/auth/domain/model/User.kt, .../repository/AuthRepository.kt, .../usecase/LoginUseCase.kt, .../usecase/SignUpUseCase.kt, .../AuthValidator.kt
User 모델을 Long id·role·access/refresh token·expiresIn 포함으로 개편. 리포지토리에 signUp 추가. SignInUseCase → LoginUseCase로 이름 변경. SignUpUseCase·입력 검증 유틸 추가
Auth UI - Login
app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt, .../LoginViewModel.kt, .../LoginUiState.kt
로그인 UI 재구성(로고, 필드, 상단 스낵바), onNavigateSignUp 콜백 추가. 뷰모델에 라벨 바인딩·실시간 검증·서버 메시지 파싱·에러 소비 로직 추가; 상태에 필드별 오류·isValid 도입
Auth UI - SignUp
app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt, .../SignUpViewModel.kt, .../SignUpUiState.kt, .../SignUpUiEvent.kt
회원가입 화면·뷰모델·상태·이벤트 신규 추가. 입력 검증, Submit 흐름, 서버 에러 파싱, 성공 콜백 처리
Resources - Drawables
app/src/main/res/drawable/ic_arrow_back.xml, .../oneline_logo.xml, .../outline_close.xml, .../square_logo.xml
로고·아이콘 벡터 리소스 추가
Resources - Colors
app/src/main/res/values/colors.xml, app/src/main/res/values-night/colors.xml
기존 보라/티얼 계열 제거, Main 계열·그레이·상태·배경·텍스트 색상 대거 추가
Resources - Strings
app/src/main/res/values/strings.xml
로그인·회원가입 레이블·플레이스홀더·버튼 및 검증 메시지·nav_back 등 문자열 추가/수정
Build / Dependencies
app/build.gradle.kts, gradle/libs.versions.toml
buildConfig 활성화, OkHttp logging-interceptor 및 AndroidX DataStore Preferences 의존성·버전 추가

Sequence Diagram(s)

sequenceDiagram
  actor U as 사용자
  participant L as LoginScreen
  participant VM as LoginViewModel
  participant UC as LoginUseCase
  participant R as AuthRepository
  participant API as AuthApi
  participant P as AuthPreferences
  participant Nav as NavHost

  U->>L: 이메일/비밀번호 입력
  L->>VM: EmailChanged/PasswordChanged
  VM-->>L: 상태(오류/유효성)
  U->>L: 로그인 클릭
  L->>VM: Submit
  VM->>UC: invoke(email,password)
  UC->>R: signIn(...)
  R->>API: POST /login
  API-->>R: ApiResponse<LoginResponseDto>
  R->>P: saveToken(accessToken, refreshToken)
  R-->>UC: User
  UC-->>VM: User
  VM-->>L: success=true
  L->>Nav: 홈으로 이동 (백스택 정리)
  alt 오류
    VM->>VM: serverMessageOrNull() 호출
    VM-->>L: errorMessage
    L->>L: ShowErrorSnackBar 표시
  end
Loading
sequenceDiagram
  actor U as 사용자
  participant S as SignUpScreen
  participant VM as SignUpViewModel
  participant UC as SignUpUseCase
  participant R as AuthRepository
  participant API as AuthApi
  participant P as AuthPreferences
  participant Nav as NavHost

  U->>S: 가입 폼 입력
  S->>VM: 필드별 Changed 이벤트
  VM-->>S: 상태(오류/유효성)
  U->>S: 가입 클릭
  S->>VM: Submit
  VM->>UC: invoke(...)
  UC->>R: signUp(...)
  R->>API: POST /signup
  API-->>R: ApiResponse<SignUpResponseDto>
  R->>R: signIn(email,password)
  R->>API: POST /login
  API-->>R: ApiResponse<LoginResponseDto>
  R->>P: saveToken(accessToken, refreshToken)
  R-->>UC: User
  UC-->>VM: User
  VM-->>S: success=true
  S->>Nav: 홈으로 이동 (백스택 정리)
  alt 오류
    VM->>VM: serverMessageOrNull() 호출
    VM-->>S: errorMessage
    S->>S: ShowErrorSnackBar 표시
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Auto/Sampoom-Management-Android#11 — AppNavHost·UI 컴포넌트 변경과 네비게이션 관련 충돌 가능성(경로/시작 목적지 및 버튼/TextField API 변경).

Suggested reviewers

  • Lee-Jong-Jin
  • yangjiseonn
  • vivivim
  • taemin3

Poem

(\_/)
(•ᴗ•) 가입창에 깡충, 로그인 창에 폴짝.
🥕 버튼은 내용으로 빛나고, 스낵바는 위에서 속삭이네.
토큰 두 개 품고 집으로 깡충깡충.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 제목은 PR의 핵심인 로그인 기능 구현을 명확하게 나타내고 변경 이력을 빠르게 파악할 수 있게 합니다. 이 PR에는 회원가입 흐름, 네비게이션 경로 추가, UI 및 테마 컴포넌트 개선 등 로그인 외 변경 사항도 광범위하게 포함되어 있습니다. 제목이 전체 변경 범위를 모두 반영하지 않으나 PR의 일부분을 정확히 요약하고 있어 부분적으로 관련성이 있는 것으로 판단됩니다.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch SPM-20

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e2a64b and af51b99.

📒 Files selected for processing (1)
  • app/src/main/java/com/sampoom/android/core/ui/theme/Type.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/sampoom/android/core/ui/theme/Type.kt

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Nitpick comments (20)
app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt (1)

6-13: 시그니처 추가 방향은 적절합니다. 다만 파라미터 객체로 캡슐화하는 게 안전합니다.

6개 위치 파라미터는 순서 실수·회귀 위험이 큽니다. SignUpParams 같은 DTO로 묶고 한 파라미터만 받는 형태를 권장합니다. 향후 필드 추가/옵셔널 처리도 유연해집니다. 에러 표준화가 필요하다면 Result/Either 반환도 고려해 주세요.

적용 예(diff):

-    suspend fun signUp(
-        name: String,
-        workspace: String,
-        branch: String,
-        position: String,
-        email: String,
-        password: String
-    ): User
+    suspend fun signUp(params: SignUpParams): User

파일 외 코드(예시):

data class SignUpParams(
    val name: String,
    val workspace: String,
    val branch: String,
    val position: String,
    val email: String,
    val password: String
)
app/src/main/res/drawable/square_logo.xml (1)

1-12: 벡터 리소스 추가 좋습니다. 색상 하드코딩은 리소스/테마 참조로 바꾸세요.

#8080FF@color/…로 추출해 라이트/다크 테마 일관성을 확보하는 것을 권장합니다. 필요 시 뷰에서 tint로 테마 색을 적용하는 방식도 고려해 주세요.

app/src/main/java/com/sampoom/android/core/ui/theme/Type.kt (2)

3-3: 불필요한 import로 보입니다.

import android.R.attr.fontFamily는 사용되지 않아 보이며 혼동을 줄 수 있으니 제거를 권장합니다.


12-16: 폰트 웨이트 매핑 보정: medium 리소스를 FontWeight.Medium으로

 val GmarketSansFamily = FontFamily(
     Font(R.font.gmarket_sans_light, FontWeight.Light),
-    Font(R.font.gmarket_sans_medium, FontWeight.Normal),
+    Font(R.font.gmarket_sans_medium, FontWeight.Medium),
     Font(R.font.gmarket_sans_bold, FontWeight.Bold)
 )
app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/SignUpResponseDto.kt (1)

1-7: 직렬화·난독화 대비

  • 현재 release { isMinifyEnabled = false }로 난독화가 비활성화되어 있어 당장은 필드명이 변경되지 않지만, 향후 minify 활성화 시 DTO 필드가 변경될 수 있으므로 @Keep 또는 ProGuard keep 룰에 클래스 추가를 권장합니다.
  • API 응답 필드명이 코드와 달라질 수 있는 경우 @SerializedName("서버필드명") 명시도 고려해주세요.
app/src/main/res/drawable/ic_arrow_back.xml (1)

1-5: XML 포맷팅 개선을 권장합니다.

벡터 드로어블이 기능적으로는 올바르며, android:autoMirrored="true"를 사용하여 RTL 언어를 지원하는 것도 좋습니다. 하지만 가독성을 위해 XML을 여러 줄로 포맷팅하는 것이 좋습니다.

다음과 같이 포맷팅을 개선할 수 있습니다:

-<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
-      
-    <path android:fillColor="@color/text" android:pathData="M313,520L537,744L480,800L160,480L480,160L537,216L313,440L800,440L800,520L313,520Z"/>
-    
-</vector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:autoMirrored="true"
+    android:tint="#000000">
+    <path
+        android:fillColor="@color/text"
+        android:pathData="M313,520L537,744L480,800L160,480L480,160L537,216L313,440L800,440L800,520L313,520Z"/>
+</vector>
app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt (2)

19-22: 타이포 수정: singUpsignUp.

가독성을 위해 의존성 이름을 표준 철자로 교정하세요. Hilt는 타입으로 주입하므로 리네임에 안전합니다.

다음과 같이 변경:

-class SignUpViewModel @Inject constructor(
-    private val singUp: SignUpUseCase,
+class SignUpViewModel @Inject constructor(
+    private val signUp: SignUpUseCase,
     private val application: Application
 ) : ViewModel() {
@@
-            singUp(
+            signUp(
                 name = s.name,
                 workspace = s.workspace,
                 branch = s.branch,

55-63: 빈 else 블록 제거(Detekt 경고).

PasswordChanged 처리부의 else가 비어 있습니다. 불필요 블록을 제거하세요. detekt.empty-blocks.EmptyElseBlock 경고와 일치합니다.

detekt 경고 기반

         is SignUpUiEvent.PasswordChanged -> {
             _state.value = _state.value.copy(password = e.password)
             validatePassword()
             if (_state.value.passwordCheck.isNotBlank()) {
                 validatePasswordCheck()
-            } else {
-
-            }
+            }
         }
app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt (1)

193-201: 제출 버튼 활성화 정책 재고(선택).

현재 enabled = state.isValid && !state.loading이라 미입력 시 제출 시도가 불가해 에러 노출 타이밍이 늦습니다. UX상 에러를 제출 시점에 보여주려면 !state.loading만으로 두고 VM에서 검증/에러 세팅을 권장합니다.

-    enabled = state.isValid && !state.loading,
+    enabled = !state.loading,
app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt (2)

20-21: 타이포 수정: singInsignIn.

의미 보존 및 가독성 향상을 위해 변수명을 표준 철자로 변경하세요.

-class LoginViewModel @Inject constructor(
-    private val singIn: LoginUseCase,
+class LoginViewModel @Inject constructor(
+    private val signIn: LoginUseCase,
     private val application: Application
 ) : ViewModel() {
@@
-        runCatching { singIn(s.email, s.password) }
+        runCatching { signIn(s.email, s.password) }

48-60: 로그인 입력 검증 강화(이메일 형식/비밀번호 정책 적용).

현재 validateNotEmpty만 사용합니다. 회원가입과 동일한 validateEmail/validatePassword를 사용해 일관성/정확성을 높이세요.

-    private fun validateEmail() {
-        val result = AuthValidator.validateNotEmpty(_state.value.email, emailLabel)
+    private fun validateEmail() {
+        val result = AuthValidator.validateEmail(_state.value.email)
         _state.value = _state.value.copy(
             emailError = result.toErrorMessage()
         )
     }
 
-    private fun validatePassword() {
-        val result = AuthValidator.validateNotEmpty(_state.value.password, passwordLabel)
+    private fun validatePassword() {
+        val result = AuthValidator.validatePassword(_state.value.password)
         _state.value = _state.value.copy(
             passwordError = result.toErrorMessage()
         )
     }
app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt (3)

69-73: SnackbarHost는 Scaffold의 snackbarHost 슬롯에 배치하세요.

현재 TopSnackBarHost를 content 내부에 렌더링합니다. Scaffold의 snackbarHost 슬롯을 사용하면 레이아웃/인셋 및 z-order가 일관됩니다.

-    Scaffold(
-//        contentWindowInsets = WindowInsets.ime,
-//        snackbarHost = { CommonSnackBarHost(snackBarHostState) }
-    ) { innerPadding ->
+    Scaffold(
+        snackbarHost = { TopSnackBarHost(snackBarHostState, extraTopPadding = 16.dp) }
+    ) { innerPadding ->
@@
-        TopSnackBarHost(snackBarHostState, extraTopPadding = 16.dp)

Also applies to: 165-166


132-147: 회원가입 텍스트: 주석(annotation)만 추가되고 실제 클릭 처리가 문장 전체에만 적용됩니다.

  • 문장 전체를 클릭 가능하게 둘 계획이면 pushStringAnnotation/pop은 불필요합니다.
  • “회원가입” 부분만 클릭 가능해야 한다면 ClickableText로 범위를 처리하세요.

아래는 주석 제거(간소화) 예시입니다.

-    pushStringAnnotation(tag = "SIGNUP", annotation = "signup")
     withStyle(
         style = SpanStyle(
             color = Main500,
             textDecoration = TextDecoration.Underline
         )
     ) {
         append(stringResource(R.string.login_signup))
     }
-    append(stringResource(R.string.login_do))
-    pop()
+    append(stringResource(R.string.login_do))

또는 ClickableText로 부분 클릭 처리 원하시면 알려주세요. 필요한 코드 제공 가능합니다.

Also applies to: 153-159


99-106: 이메일 입력의 키보드 타입 지정 권장.

CommonTextField는 비밀번호가 아니면 KeyboardType.Text를 사용합니다. 로그인 UX를 위해 이메일 필드에 KeyboardType.Email 적용을 권합니다. 현재 CommonTextField에 keyboardOptions 파라미터가 없어 직접 지정이 어려우니, API에 keyboardOptions: KeyboardOptions = KeyboardOptions.Default를 추가하고 호출부에서 Email을 전달하는 개선을 제안합니다.

원하시면 CommonTextField와 호출부(LoginScreen)까지 포함한 패치 제안 드리겠습니다.

Also applies to: 108-116

app/src/main/java/com/sampoom/android/feature/auth/domain/AuthValidator.kt (1)

71-71: 다국어 메시지 포맷 인자를 확장 가능하게.

단일 Any 보다는 가변 인자(vararg)가 향후 확장에 유리합니다(예: "%1$s (%2$s) ...").

-    data class ErrorWithArgs(val messageResId: Int, val args: Any) : ValidationResult()
+    data class ErrorWithArgs(val messageResId: Int, vararg val args: Any) : ValidationResult()

호출부 예: ValidationResult.ErrorWithArgs(R.string.validation_field_required, fieldName)

app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt (3)

3-4: 불필요/혼동을 유발할 수 있는 import는 제거하세요.

android.R.attr.onClick, android.R.attr.text는 사용되지 않고 혼동을 줍니다.

-import android.R.attr.onClick
-import android.R.attr.text

29-47: KDoc 사용 예시가 최신 API와 불일치합니다.

현재는 content 슬롯 기반입니다. text = "Button" 예시는 혼란을 줍니다. 최신 사용법으로 갱신해주세요.


120-131: Ghost 버튼의 활성 색상으로 disableColor 사용은 비직관적입니다.

활성 상태에서는 textSecondaryColor() 등 의도된 온서피스 색상이 더 자연스럽습니다.

-                colors = ButtonDefaults.textButtonColors(
-                    contentColor = disableColor(),
-                    disabledContentColor = textSecondaryColor()
-                )
+                colors = ButtonDefaults.textButtonColors(
+                    contentColor = textSecondaryColor(),
+                    disabledContentColor = disableColor()
+                )
app/src/main/java/com/sampoom/android/core/ui/component/CommonTextField.kt (2)

3-5: 불필요/오류 가능성이 있는 import 정리 필요.

android.R.attr.singleLine, android.R.attr.text는 사용되지 않습니다.
InputTransformation.Companion.keyboardOptions는 사용되지 않고 버전에 따라 존재하지 않을 수 있어 컴파일 이슈를 유발할 수 있습니다.

-import android.R.attr.singleLine
-import android.R.attr.text
@@
-import androidx.compose.foundation.text.input.InputTransformation.Companion.keyboardOptions

Also applies to: 10-11


31-41: keyboardOptions를 파라미터로 노출하는 확장 제안(이메일 키보드 등).

호출부에서 이메일/전화/숫자 등 키보드 타입을 지정할 수 있도록 API를 확장하면 재사용성이 올라갑니다.

예시:

 fun CommonTextField(
     modifier: Modifier = Modifier,
     value: String,
     onValueChange: (String) -> Unit,
     placeholder: String,
     enabled: Boolean = true,
     isPassword: Boolean = false,
     variant: TextFieldVariant = TextFieldVariant.Outlined,
     isError: Boolean = false,
-    errorMessage: String? = null
+    errorMessage: String? = null,
+    keyboardOptions: KeyboardOptions = KeyboardOptions.Default
 ) {
@@
-                keyboardOptions = KeyboardOptions(keyboardType = if (isPassword) KeyboardType.Password else KeyboardType.Text),
+                keyboardOptions = if (isPassword)
+                    KeyboardOptions(keyboardType = KeyboardType.Password)
+                else keyboardOptions,
@@
-                keyboardOptions = KeyboardOptions(keyboardType = if (isPassword) KeyboardType.Password else KeyboardType.Text),
+                keyboardOptions = if (isPassword)
+                    KeyboardOptions(keyboardType = KeyboardType.Password)
+                else keyboardOptions,

LoginScreen의 이메일 입력에서는 keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)로 지정 가능.

Also applies to: 91-93, 121-123

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac9c71e and 989065d.

⛔ Files ignored due to path filters (3)
  • app/src/main/res/font/gmarket_sans_bold.otf is excluded by !**/*.otf
  • app/src/main/res/font/gmarket_sans_light.otf is excluded by !**/*.otf
  • app/src/main/res/font/gmarket_sans_medium.otf is excluded by !**/*.otf
📒 Files selected for processing (36)
  • app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (3 hunks)
  • app/src/main/java/com/sampoom/android/core/network/ErrorHandling.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/network/NetworkModule.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt (14 hunks)
  • app/src/main/java/com/sampoom/android/core/ui/component/CommonSnackBar.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/ui/component/CommonTextField.kt (3 hunks)
  • app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/ui/theme/Theme.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/ui/theme/Type.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/local/preferences/AuthPreferences.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/mapper/AuthMappers.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/LoginResponseDto.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/SignUpRequestDto.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/SignUpResponseDto.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/UserDto.kt (0 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt (2 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/domain/AuthValidator.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/domain/model/User.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/LoginUseCase.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/SignUpUseCase.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/ui/LoginUiState.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt (2 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiEvent.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiState.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt (1 hunks)
  • app/src/main/res/drawable/ic_arrow_back.xml (1 hunks)
  • app/src/main/res/drawable/oneline_logo.xml (1 hunks)
  • app/src/main/res/drawable/outline_close.xml (1 hunks)
  • app/src/main/res/drawable/square_logo.xml (1 hunks)
  • app/src/main/res/values-night/colors.xml (1 hunks)
  • app/src/main/res/values/colors.xml (1 hunks)
  • app/src/main/res/values/strings.xml (2 hunks)
💤 Files with no reviewable changes (1)
  • app/src/main/java/com/sampoom/android/feature/auth/data/remote/dto/UserDto.kt
🧰 Additional context used
🧬 Code graph analysis (9)
app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt (1)
app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt (1)
  • signIn (15-15)
app/src/main/java/com/sampoom/android/core/ui/component/CommonTextField.kt (1)
app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt (1)
  • backgroundColor (34-35)
app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt (3)
app/src/main/java/com/sampoom/android/core/ui/component/CommonSnackBar.kt (3)
  • rememberCommonSnackBarHostState (35-36)
  • ShowErrorSnackBar (38-60)
  • TopSnackBarHost (62-111)
app/src/main/java/com/sampoom/android/core/ui/component/CommonTextField.kt (1)
  • CommonTextField (29-138)
app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt (1)
  • CommonButton (48-154)
app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt (2)
app/src/main/java/com/sampoom/android/feature/auth/domain/AuthValidator.kt (3)
  • validateEmail (7-18)
  • validatePassword (21-43)
  • validatePasswordCheck (46-56)
app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt (3)
  • validateEmail (48-53)
  • validatePassword (55-60)
  • submit (70-87)
app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt (2)
app/src/main/java/com/sampoom/android/feature/auth/domain/AuthValidator.kt (2)
  • validateEmail (7-18)
  • validatePassword (21-43)
app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt (3)
  • validateEmail (92-97)
  • validatePassword (99-104)
  • submit (121-151)
app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt (1)
app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt (2)
  • disableColor (46-47)
  • textSecondaryColor (43-44)
app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt (3)
app/src/main/java/com/sampoom/android/core/ui/component/CommonSnackBar.kt (3)
  • rememberCommonSnackBarHostState (35-36)
  • ShowErrorSnackBar (38-60)
  • TopSnackBarHost (62-111)
app/src/main/java/com/sampoom/android/core/ui/component/CommonTextField.kt (1)
  • CommonTextField (29-138)
app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt (1)
  • CommonButton (48-154)
app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (1)
app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt (1)
  • SignUpScreen (45-209)
app/src/main/java/com/sampoom/android/core/ui/component/CommonSnackBar.kt (1)
app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt (2)
  • backgroundCardColor (37-38)
  • textColor (40-41)
🪛 detekt (1.23.8)
app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt

[warning] 60-62: This empty block of code can be removed.

(detekt.empty-blocks.EmptyElseBlock)

🔇 Additional comments (15)
app/src/main/res/drawable/oneline_logo.xml (1)

1-12: 벡터 드로어블 리소스가 올바르게 정의되었습니다.

로고 리소스가 적절히 구성되어 있으며, @color/text를 사용하여 테마 시스템과 일관성을 유지하고 있습니다.

app/src/main/res/values/strings.xml (4)

5-5: 네비게이션 문자열 추가가 적절합니다.

뒤로가기 문자열이 추가되어 접근성을 개선합니다.


13-21: 로그인 관련 문자열이 잘 구성되었습니다.

플레이스홀더 텍스트가 "이메일"에서 "이메일 입력"으로 변경되어 사용자에게 더 명확한 안내를 제공합니다. 회원가입 링크 관련 문자열도 적절히 추가되었습니다.


23-38: 회원가입 문자열이 포괄적으로 추가되었습니다.

이름, 지점, 직급, 이메일, 비밀번호 등 모든 필수 필드에 대한 타이틀과 플레이스홀더가 일관되게 정의되어 있습니다.


48-57: 유효성 검증 메시지가 명확하게 정의되었습니다.

이메일, 비밀번호 유효성 검증에 필요한 모든 메시지가 포함되어 있으며, validation_field_required는 형식 문자열(%s)을 사용하여 재사용성을 높였습니다.

app/src/main/java/com/sampoom/android/core/ui/theme/Theme.kt (2)

13-17: 다크 모드 컬러 스킴이 새로운 Main 팔레트로 업데이트되었습니다.

Purple/Pink 계열에서 Main500/Main300/Main100으로 변경되어 일관된 브랜드 컬러를 적용합니다.


19-23: 라이트 모드 컬러 스킴이 다크 모드와 일관되게 업데이트되었습니다.

동일한 Main 팔레트를 사용하여 테마 간 일관성을 유지합니다.

app/src/main/java/com/sampoom/android/feature/auth/domain/usecase/LoginUseCase.kt (1)

7-12: 유스케이스가 깔끔하게 구현되었습니다.

SignInUseCase에서 LoginUseCase로 명확하게 이름이 변경되었으며, 리포지토리에 대한 단순한 위임을 통해 도메인 계층의 관심사를 잘 분리하고 있습니다.

app/src/main/java/com/sampoom/android/feature/auth/data/remote/api/AuthApi.kt (2)

15-16: 회원가입 API가 올바르게 추가되었습니다.

새로운 회원가입 엔드포인트가 적절하게 정의되었으며, ApiResponse 래퍼를 사용하여 일관된 응답 처리를 지원합니다.


12-13: ```bash
#!/bin/bash

Kotlin 소스에서 이전 로그인 엔드포인트 참조 검색

echo "Kotlin(.kt) 파일에서 auth/login 검색:"
rg -n "auth/login" -g '*.kt' || echo "Kotlin 파일에서 참조 없음"

echo -e "\n설정 파일에서 auth/login 검색:"
fd -e properties -e xml -e json | xargs -r rg -l "auth/login" 2>/dev/null || echo "설정 파일에서 참조 없음"


</blockquote></details>
<details>
<summary>app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiEvent.kt (2)</summary><blockquote>

`3-11`: **이벤트 모델링 전반은 적절합니다.**

입력 변경/제출 이벤트 분리가 명확합니다. 전반 LGTM입니다.

---

`10-10`: **data object 사용 호환성 확인**  
`data object Submit`은 Kotlin 1.9+ 기능입니다. build.gradle(.kts) 또는 gradle.properties에서 Kotlin 플러그인 버전이 1.9 이상인지 확인하거나, 호환성을 우선한다면 `object Submit : SignUpUiEvent`로 변경하세요.  

```shell
#!/bin/bash
rg -n 'kotlin_version' -g 'gradle.properties'
rg -n 'id\("org.jetbrains.kotlin' -g '**/*.gradle.kts' -g '**/*.gradle'
app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpUiState.kt (1)

3-37: 상태 모델링 LGTM.

필드/에러/플래그 구성과 isValid 파생 속성 구성이 명확합니다.

app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt (1)

62-68: ErrorWithArgs.args는 Any 타입으로 spread 불필요
현재 args가 배열이 아니므로 *args 사용 시 컴파일 에러가 발생합니다. 여러 포맷 인자를 지원하려면 argsvararg args: Any 또는 Array<Any>로 변경한 뒤 spread를 적용하세요.

Likely an incorrect or invalid review comment.

app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpViewModel.kt (1)

113-119: ErrorWithArgs.args는 Any 타입이므로 spread 연산 불필요
args가 vararg가 아닌 Any로 선언되어 있어 *args 사용 시 컴파일 에러가 발생합니다. 기존 코드 그대로 유지하세요.

Likely an incorrect or invalid review comment.

Comment thread app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt
Comment thread app/src/main/java/com/sampoom/android/core/network/NetworkModule.kt
Comment thread app/src/main/java/com/sampoom/android/core/ui/theme/Type.kt Outdated
Comment thread app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt Outdated
Comment thread app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt Outdated
Comment thread app/src/main/res/drawable/outline_close.xml Outdated
Copy link
Copy Markdown
Member

@CHOOSLA CHOOSLA left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt (1)

29-47: 문서를 업데이트하여 변경된 API를 반영하세요.

KDoc이 여전히 제거된 text 파라미터를 참조하고 있습니다. 새로운 content 파라미터를 반영하도록 문서를 업데이트해야 합니다.

다음 diff를 적용하여 문서를 업데이트하세요:

  * Usage
  * -----
  * CommonButton(
- *   text = "Button",
+ *   content = { Text("Button", fontWeight = FontWeight.Bold) },
  *   variant = ButtonVariant.Primary,
  *   onClick = { ... }
  * )
  *
  * Optionally pass a leading icon:
  * CommonButton(
- *   text = "Button",
+ *   content = { Text("Button", fontWeight = FontWeight.Bold) },
  *   variant = ButtonVariant.Primary,
  *   leadingIcon = { Icon(painterResource(R.drawable.parts), contentDescription = null) },
  *   onClick = { ... }
  * )
♻️ Duplicate comments (1)
app/src/main/java/com/sampoom/android/feature/auth/data/local/preferences/AuthPreferences.kt (1)

28-34: 보안: 토큰은 암호화 저장이 필요합니다 (DataStore에서도 평문 저장 지양)

현재 DataStore에 평문으로 저장됩니다. 탈취 위험이 있어 Android Keystore 기반 암호화( security-crypto )로 저장/복호화를 권장합니다.

적용 예시(핵심 아이디어):

  • 저장 시: prefs[ACCESS_TOKEN] = cipher.encrypt(accessToken)
  • 조회 시: cipher.decrypt(prefs[ACCESS_TOKEN])
-    suspend fun saveToken(accessToken: String, refreshToken: String) {
-        dataStore.edit { prefs ->
-            prefs[Keys.ACCESS_TOKEN] = accessToken
-            prefs[Keys.REFRESH_TOKEN] = refreshToken
-        }
-    }
+    suspend fun saveToken(accessToken: String, refreshToken: String) {
+        dataStore.edit { prefs ->
+            prefs[Keys.ACCESS_TOKEN] = cipher.encrypt(accessToken)
+            prefs[Keys.REFRESH_TOKEN] = cipher.encrypt(refreshToken)
+        }
+    }

-    fun getAccessToken(): String? = runBlocking {
-        dataStore.data.first()[Keys.ACCESS_TOKEN]
-    }
+    fun getAccessToken(): String? = runBlocking {
+        dataStore.data.first()[Keys.ACCESS_TOKEN]?.let(cipher::decrypt)
+    }

-    fun getRefreshToken(): String? = runBlocking {
-        dataStore.data.first()[Keys.REFRESH_TOKEN]
-    }
+    fun getRefreshToken(): String? = runBlocking {
+        dataStore.data.first()[Keys.REFRESH_TOKEN]?.let(cipher::decrypt)
+    }

파일 외부 보조 코드(예시, 실제 프로젝트에 맞게 조정 가능):

// 보조: 간단한 토큰 암복호화 헬퍼 (Android Keystore + AES/GCM)
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import android.util.Base64
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec

class TokenCipher(context: Context) {
    private val masterKey = MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()

    // 간단화를 위해 키 파생/관리 로직은 생략. 프로젝트 표준에 맞게 키관리 구현 권장.
    fun encrypt(plain: String): String { /* ... */ return plain /* placeholder */ }
    fun decrypt(enc: String?): String? { /* ... */ return enc /* placeholder */ }
}

// AuthPreferences 내부
private val cipher by lazy { TokenCipher(context) }

원하시면 실제 동작 가능한 Keystore 기반 구현과 예외 처리/마이그레이션까지 포함해 PR에 패치로 넣어드릴게요.

Also applies to: 36-43

🧹 Nitpick comments (11)
app/build.gradle.kts (1)

81-83: DataStore 의존성 추가 LGTM. 보안 강화를 위해 security-crypto 추가 권장

토큰을 평문으로 저장하지 않도록 Jetpack Security Crypto를 함께 도입해 주세요. toml에 alias 추가 후 아래와 같이 의존성도 추가하는 것을 권장합니다.

 dependencies {
     // DataStore
     implementation(libs.androidx.datastore.preferences)
+    // Security Crypto (토큰 암호화)
+    implementation(libs.androidx.security.crypto)
 }

libs.versions.toml에 androidx-security-crypto alias가 없다면 함께 추가해 주세요(아래 코멘트 참고). 최신 버전은 확인이 필요합니다.

app/src/main/java/com/sampoom/android/feature/auth/data/local/preferences/AuthPreferences.kt (3)

14-15: 기존 SharedPreferences → DataStore 마이그레이션 추가 제안

기존에 SharedPreferences를 사용했다면 DataStore로 안전하게 이관하기 위해 SharedPreferencesMigration을 붙이는 것을 권장합니다. 데이터 유실을 방지합니다.

 import androidx.datastore.preferences.preferencesDataStore
+import androidx.datastore.preferences.SharedPreferencesMigration

-// Per official guidance, DataStore instance should be single and at top-level.
-private val Context.authDataStore by preferencesDataStore(name = "auth_prefs")
+// Per official guidance, DataStore instance should be single and at top-level.
+private val Context.authDataStore by preferencesDataStore(
+    name = "auth_prefs",
+    produceMigrations = { context ->
+        listOf(
+            // 기존 SharedPreferences 파일명 확인 필요
+            SharedPreferencesMigration(context, "auth_prefs")
+        )
+    }
+)

기존 SP 파일명/키가 다르다면 맞게 바꿔야 합니다. 알려주시면 마이그레이션 코드 정확히 잡아드리겠습니다.


36-43: runBlocking 지양: suspend/Flow 기반으로 전환 권장

UI 스레드에서 호출될 경우 블로킹 위험이 있습니다. suspend 함수로 바꾸거나 Flow를 노출하세요.

-    // Synchronous getters backed by runBlocking for minimal surface change
-    fun getAccessToken(): String? = runBlocking {
-        dataStore.data.first()[Keys.ACCESS_TOKEN]
-    }
+    // Suspend getters
+    suspend fun getAccessToken(): String? =
+        dataStore.data.first()[Keys.ACCESS_TOKEN]

-    fun getRefreshToken(): String? = runBlocking {
-        dataStore.data.first()[Keys.REFRESH_TOKEN]
-    }
+    suspend fun getRefreshToken(): String? =
+        dataStore.data.first()[Keys.REFRESH_TOKEN]

-    fun hasToken(): Boolean = !getAccessToken().isNullOrEmpty() && !getRefreshToken().isNullOrEmpty()
+    suspend fun hasToken(): Boolean =
+        dataStore.data.first().let { prefs ->
+            !prefs[Keys.ACCESS_TOKEN].isNullOrEmpty() &&
+            !prefs[Keys.REFRESH_TOKEN].isNullOrEmpty()
+        }

호출부가 동기 API를 요구(예: OkHttp Interceptor)한다면 그 경로에서만 runBlocking을 최소화해 래핑하는 방식으로 국소화하는 것을 권장합니다.

Also applies to: 49-49


19-19: 주입 어노테이션 통일성(nit): @ApplicationContext 사용

의미 차이는 없지만, 일반적으로 파라미터에 직접 @ApplicationContext를 붙입니다.

-    @param:ApplicationContext private val context: Context
+    @ApplicationContext private val context: Context
gradle/libs.versions.toml (1)

4-4: DataStore 버전 추가 LGTM 및 security-crypto 라이브러리 등록 제안

DataStore 도입과 토큰 암호화를 위해 security-crypto 라이브러리(alias: securityCrypto)를 다음과 같이 추가해 주세요.

 [versions]
 datastorePreferences = "1.1.7"
+securityCrypto = "1.1.0"

 [libraries]
 androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
+androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCrypto" }
app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (2)

55-57: isLoggedIn 하드코딩 제거하고 실제 인증 상태로 결정하세요

앱 시작 목적지 결정은 저장된 토큰/세션 기반으로 ViewModel/Repository의 Flow를 구독해 반영하는 것이 안전합니다. 로딩 상태(스플래시)도 고려해 주세요.

예시 (개념 스케치):

@Composable
fun AppNavHost() {
    val authViewModel: AuthViewModel = hiltViewModel()
    val isLoggedIn by authViewModel.isLoggedIn.collectAsState(initial = false)

    NavHost(
        navController = navController,
        startDestination = if (isLoggedIn) ROUTE_HOME else ROUTE_LOGIN
    ) { /* ... */ }
}
  • 저장소: DataStore(토큰 존재), 또는 remote refresh 결과를 반영하는 Flow를 권장
  • 로딩: isLoading이 true면 Splash/Progress 화면을 startDestination로 두고, 결과에 따라 로그인/홈으로 전환

116-123: PartsFab의 popUpTo는 불필요하며, 부모 그래프 startDestinationId 사용은 잠재적 리스크입니다

부모 그래프의 startDestinationId(현재 로그인/홈 그래프)를 기준으로 popUpTo 하는 것은 의도치 않은 백스택 변형을 유발할 수 있습니다. 상세 화면 진입은 단순 navigate만으로 충분합니다.

             navController.navigate(ROUTE_PARTS) {
-                popUpTo(navController.graph.startDestinationId) {
-                    saveState = true
-                }
-                launchSingleTop = true
-                restoreState = true
+                launchSingleTop = true
             }
  • 필요 시 뒤로가기로 홈 복귀를 원하면 popUpTo 없이 단순 push가 자연스럽습니다.
  • state 복원은 바텀탭 전환 시 내부 자식 NavHost에서만 사용하세요(이미 그렇게 구현되어 있음).
app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt (2)

135-151: CTA 문자열에 공백 추가(가독성 개선) + 부분 클릭을 원하면 ClickableText 고려

현재 "계정이 없으신가요?회원가입하기"처럼 붙어 보입니다. 간단히 공백을 추가하세요.

-    val annotatedText = buildAnnotatedString {
-        append(stringResource(R.string.login_need_account))
+    val annotatedText = buildAnnotatedString {
+        append(stringResource(R.string.login_need_account))
+        append(" ")
         // 클릭 가능한 회원가입 텍스트
         pushStringAnnotation(tag = "SIGNUP", annotation = "signup")
         withStyle(
             style = SpanStyle(
                 color = Main500,
                 textDecoration = TextDecoration.Underline
             )
         ) {
             append(stringResource(R.string.login_signup))
         }
-        append(stringResource(R.string.login_do))
         pop()
     }

선택(부분 클릭만 허용): ClickableText로 SIGNUP 주석 영역만 처리

import androidx.compose.foundation.text.ClickableText

ClickableText(
    text = annotatedText,
    style = MaterialTheme.typography.bodyMedium.copy(textAlign = TextAlign.Center),
    modifier = Modifier.padding(vertical = 24.dp),
    onClick = { offset ->
        annotatedText.getStringAnnotations(tag = "SIGNUP", start = offset, end = offset)
            .firstOrNull()?.let { onNavigateSignUp() }
    }
)

72-75: IME 인셋 적용 정책을 화면 간 일관되게 맞추세요

SignUpScreen은 contentWindowInsets = WindowInsets.ime을 쓰고, LoginScreen은 Column에 imePadding()을 사용 중입니다. 중복 적용만 피하면 둘 다 가능하지만, 같은 패턴으로 통일하면 유지보수에 유리합니다.

권장:

  • 스캐폴드에 contentWindowInsets = WindowInsets.ime 지정
  • 내부 레이아웃에서는 별도의 imePadding() 제거
  • 두 화면 모두 동일 정책 적용 후 키보드 표시/숨김 시 레이아웃 이동 여부를 확인

Also applies to: 88-93

app/src/main/res/values/strings.xml (2)

37-38: 문구 일관성: “회원가입 중…”으로 통일 권장

현재 "회원 가입 중…"(띄어쓰기)로 되어 있어 "회원가입"과 불일치합니다. 다음처럼 수정하세요.

-    <string name="signup_button_signup_loading">회원 가입 중…</string>
+    <string name="signup_button_signup_loading">회원가입 중…</string>

19-22: CTA 문자열 분절 대신 단일 문자열로 관리 권장

login_need_account + login_signup + login_do 분리 구성은 언어별 어순/공백 처리에 취약합니다. “계정이 없으신가요? 회원가입하기” 같은 단일 리소스로 관리하고, 강조/클릭 범위는 Span으로 처리하는 방식을 추천합니다.

예:

  • strings.xml: <string name="login_cta_signup">계정이 없으신가요? 회원가입하기</string>
  • UI: buildAnnotatedString로 “회원가입하기” 부분에만 스타일/클릭 부여
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 989065d and 4d0f07b.

📒 Files selected for processing (13)
  • app/build.gradle.kts (1 hunks)
  • app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (3 hunks)
  • app/src/main/java/com/sampoom/android/core/network/NetworkModule.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt (14 hunks)
  • app/src/main/java/com/sampoom/android/core/ui/component/CommonSnackBar.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/ui/theme/Type.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/local/preferences/AuthPreferences.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt (2 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt (1 hunks)
  • app/src/main/res/drawable/outline_close.xml (1 hunks)
  • app/src/main/res/values/strings.xml (2 hunks)
  • gradle/libs.versions.toml (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/src/main/res/drawable/outline_close.xml
  • app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt
  • app/src/main/java/com/sampoom/android/core/ui/component/CommonSnackBar.kt
🧰 Additional context used
🧬 Code graph analysis (4)
app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (1)
app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt (1)
  • SignUpScreen (45-212)
app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt (3)
app/src/main/java/com/sampoom/android/core/ui/component/CommonSnackBar.kt (3)
  • rememberCommonSnackBarHostState (36-37)
  • ShowErrorSnackBar (39-61)
  • TopSnackBarHost (63-112)
app/src/main/java/com/sampoom/android/core/ui/component/CommonTextField.kt (1)
  • CommonTextField (29-138)
app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt (1)
  • CommonButton (48-157)
app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt (1)
app/src/main/java/com/sampoom/android/feature/auth/domain/repository/AuthRepository.kt (1)
  • signIn (15-15)
app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt (1)
app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt (2)
  • disableColor (46-47)
  • textSecondaryColor (43-44)
🔇 Additional comments (9)
app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt (1)

66-155: LGTM! leadingIcon 일관성 문제가 해결되었습니다.

모든 버튼 variant가 이제 leadingIcon을 일관되게 렌더링합니다. 이전 리뷰에서 지적된 Outlined/Ghost/Neutral variant에서 leadingIcon이 무시되던 문제가 수정되었습니다.

app/src/main/java/com/sampoom/android/core/ui/theme/Type.kt (2)

19-35: 이전 리뷰 이슈가 올바르게 수정되었습니다!

baseTypography를 활용하여 Material3의 기본 값(fontSize, lineHeight, letterSpacing)을 보존하면서 fontFamily만 GmarketSansFamily로 교체하는 방식으로 정확하게 구현되었습니다. 이전 리뷰에서 제기된 UI 깨짐 위험이 해결되었습니다.

Based on past review comments.


12-16: 폰트 리소스 확인 완료

app/src/main/res/font/에 필요한 gmarket_sans_light, gmarket_sans_medium, gmarket_sans_bold 파일이 모두 존재합니다. 해당 정의를 그대로 유지하면 됩니다.

app/src/main/java/com/sampoom/android/core/network/NetworkModule.kt (1)

21-23: ⚠ LOWER_CASE_WITH_UNDERSCORES 전역 적용 — DTO 매핑 호환성 검증 필요

  • 프로젝트의 모든 data class@SerializedName이 없어, 서버 JSON 필드 네이밍(snake_case)과 정확히 일치하는지 확인하세요
  • 불일치 위험이 있는 필드는 @SerializedName("json_field_name")을 명시적으로 지정하는 방안을 고려하세요
app/src/main/java/com/sampoom/android/feature/auth/data/repository/AuthRepositoryImpl.kt (3)

40-41: 변경사항 확인 완료

DTO 구조가 중첩된 data 필드를 사용하도록 변경된 것에 맞춰 토큰 저장과 모델 변환 로직이 일관되게 업데이트되었습니다.


44-44: 코드 스타일 개선

단일 라인으로 간결하게 리팩토링되었습니다.


16-33: SignUpResponseDto를 무시하고 곧바로 signIn 호출하는 것은 의도된 설계입니다
SignUp API는 토큰을 반환하지 않으므로, signUp 성공 후 즉시 signIn을 통해 User를 반환하도록 한 레포지토리 인터페이스 설계에 부합합니다.

app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (1)

63-70: 인증 성공 시 백스택 정리 LGTM

popUpTo(ROUTE_LOGIN) { inclusive = true }로 로그인·회원가입 스택이 모두 제거되어 홈에서 뒤로가기도 자연스럽습니다. 회원가입 플로우까지 동일하게 처리된 점 좋습니다.

Also applies to: 73-82

app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt (1)

62-64: 네비게이션 트리거 처리 적절합니다

LaunchedEffect(state.success)로 재조합 중복 호출을 방지한 점 좋습니다.

Comment thread app/src/main/java/com/sampoom/android/core/network/NetworkModule.kt Outdated
Comment thread app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt Outdated
Comment thread app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt Outdated
Comment thread app/src/main/java/com/sampoom/android/core/ui/theme/Type.kt Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt (1)

26-44: KDoc 예제를 현재 API에 맞게 업데이트하세요.

주석의 사용 예제가 여전히 제거된 text 파라미터를 참조하고 있습니다. 실제 API는 content 파라미터를 사용하므로 문서를 업데이트해야 합니다.

다음 diff를 적용하여 KDoc을 업데이트하세요:

 /**
  * Sampoom common button with multiple visual variants.
  *
  * Usage
  * -----
  * CommonButton(
- *   text = "Button",
  *   variant = ButtonVariant.Primary,
  *   onClick = { ... }
+ * ) {
+ *   Text("Button")
+ * }
  *
  * Optionally pass a leading icon:
  * CommonButton(
- *   text = "Button",
  *   variant = ButtonVariant.Primary,
  *   leadingIcon = { Icon(painterResource(R.drawable.parts), contentDescription = null) },
  *   onClick = { ... }
+ * ) {
+ *   Text("Button")
+ * }
  */
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d0f07b and 2e2a64b.

📒 Files selected for processing (5)
  • app/build.gradle.kts (2 hunks)
  • app/src/main/java/com/sampoom/android/core/network/NetworkModule.kt (1 hunks)
  • app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt (14 hunks)
  • app/src/main/java/com/sampoom/android/core/ui/theme/Type.kt (1 hunks)
  • gradle/libs.versions.toml (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • gradle/libs.versions.toml
  • app/src/main/java/com/sampoom/android/core/network/NetworkModule.kt
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt (1)
app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt (2)
  • disableColor (46-47)
  • textSecondaryColor (43-44)
🔇 Additional comments (4)
app/build.gradle.kts (2)

56-58: BuildConfig 옵션 활성화 확인 완료

로그인/네트워크 설정에 필요한 BuildConfig 상수 사용을 위한 설정으로 보이며, 기존 설정과 충돌도 없습니다. 그대로 진행하셔도 됩니다.


82-87: 신규 의존성 추가 적절합니다

네트워크 로깅 및 DataStore 기반 인증 정보 저장 흐름과 맞물린 의존성 추가로 보이며, 스코프나 구성에서도 문제 없습니다. 👍

app/src/main/java/com/sampoom/android/core/ui/component/CommonButton.kt (2)

82-98: Secondary variant의 색상이 디자인 의도와 일치하는지 확인하세요.

Secondary variant가 "Filled tonal (light purple)"로 문서화되어 있지만, Primary와 동일한 Main500containerColor로 사용하고 있습니다. FilledTonalButton은 일반적으로 Primary보다 연한 색상(예: Main100 또는 Main200)을 사용하여 시각적 위계를 구분합니다.

디자인 시스템에서 Secondary variant가 Primary와 동일한 색상을 의도한 것인지 확인하고, 만약 다른 색상을 사용해야 한다면 다음과 같이 수정하세요:

colors = ButtonDefaults.filledTonalButtonColors(
    containerColor = Main100, // 또는 디자인에 맞는 더 연한 톤
    contentColor = White,
    disabledContainerColor = disableColor(),
    disabledContentColor = textSecondaryColor()
)

62-152: 이전 리뷰의 모든 문제가 해결되었습니다!

과거 리뷰에서 지적된 문제들이 모두 수정되었습니다:

  • leadingIcon이 이제 모든 variant(Primary, Secondary, Outlined, Ghost, Neutral)에서 일관되게 렌더링됩니다.
  • ✅ 사용되지 않는 import(android.R.attr.onClick, android.R.attr.text)가 제거되었습니다.
  • ✅ Ghost variant가 활성화 상태에서 올바른 색상(textSecondaryColor())을 사용합니다.

현재 구현은 일관되고 명확합니다.

Comment thread app/src/main/java/com/sampoom/android/core/ui/theme/Type.kt
Copy link
Copy Markdown

@taemin3 taemin3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다!

Copy link
Copy Markdown

@yangjiseonn yangjiseonn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge 3명 이상의 리뷰어에게 승인되어 병합 준비가 완료된 PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants