부품 조회화면 구현#16
Conversation
Walkthrough네비게이션에 부품 목록 라우트를 추가하고, 부품 도메인을 카테고리/그룹/부품 계층으로 재설계했습니다. API 경로·DTO·매퍼·저장소·유스케이스·뷰모델·UI가 계층 구조에 맞게 변경되었고, 테마 색상 토큰이 대폭 확장되었으며 인증 엔드포인트 경로가 갱신되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as PartScreen
participant VM as PartViewModel
participant GetCat as GetCategoryUseCase
participant GetGroup as GetGroupUseCase
participant Repo as PartRepository
User->>UI: 화면 진입
UI->>VM: onEvent(LoadCategories)
VM->>GetCat: invoke()
GetCat->>Repo: getCategoryList()
Repo-->>GetCat: CategoryList
GetCat-->>VM: CategoryList
VM-->>UI: uiState(categoryList)
User->>UI: 카테고리 선택
UI->>VM: onEvent(CategorySelected)
VM->>GetGroup: invoke(categoryId)
GetGroup->>Repo: getGroupList(categoryId)
Repo-->>GetGroup: GroupList
GetGroup-->>VM: GroupList
VM-->>UI: uiState(groupList)
User->>UI: 그룹 클릭
UI->>UI: onNavigatePartList(group)
UI->>AppNavHost: navigate(routePartList(...))
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (23)
app/src/main/res/drawable/body.xml (1)
2-5: 아이콘 크기·틴팅·호환성 점검
- 머티리얼 아이콘 기준 24dp가 기본입니다. 25dp는 정렬 이슈를 유발할 수 있어 24dp로 권장합니다.
- 고정 색(@color/text) 대신 테마 틴트 사용을 고려해 주세요(Compose에서 tint 적용 또는 벡터에 ?attr/colorOnSurface 등). 다크/라이트에서 대비 확보에 유리합니다.
- clip-path 사용은 하위 API/VectorDrawableCompat에서 제약이 있을 수 있습니다. 프로젝트 minSdk가 해당 기능을 충분히 커버하는지 확인 부탁드립니다.
Also applies to: 10-17
app/src/main/res/values/strings.xml (1)
41-48: 라벨 표기·톤앤매너 정리
- "부품조회"는 일반적으로 띄어쓰기 "부품 조회"가 자연스럽습니다. part_title을 "부품 조회"로 제안합니다.
- 비어있는 상태 문구들은 모두 종결부호로 일관되어 좋습니다. 추후 검색 placeholder 톤도 다른 화면과 통일(예: "~ 입력" vs "~으로 검색")되었는지 한번 더 점검해 주세요.
Also applies to: 54-54
app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt (1)
4-4: 불필요한 import 제거 필요검증 결과,
androidx.compose.foundation.background(4줄)과com.sampoom.android.core.ui.theme.backgroundColor(45줄) 두 import 모두 코드에서 실제로 사용되지 않습니다. 파일 전체에서.background(또는backgroundColor()호출이 없습니다. 이 두 import를 제거하세요.app/src/main/java/com/sampoom/android/feature/part/ui/PartListUiEvent.kt (1)
3-6: UI 이벤트 구조가 명확합니다.Sealed interface 패턴이 적절하게 사용되었습니다. 선택적으로, 더 나은 디버깅을 위해
object대신data object를 사용할 수 있습니다.선택적 개선안:
sealed interface PartListUiEvent { - object LoadPartList : PartListUiEvent - object RetryPartList : PartListUiEvent + data object LoadPartList : PartListUiEvent + data object RetryPartList : PartListUiEvent }app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetCategoryUseCase.kt (1)
7-11: 심플한 위임 형태, OK. 다만 이름/KDoc 보완 제안현재 구현은 깨끗합니다. 향후 검색·필터 옵션이 추가될 가능성을 고려하면 함수/클래스명을 GetCategoriesUseCase 또는 FetchCategoryListUseCase로 명확히 하거나 KDoc로 반환/예외 정책을 명시해 주세요.
app/src/main/java/com/sampoom/android/feature/part/ui/PartListUiState.kt (1)
5-9: UI 상태 불변성/일관성 강화 제안
- 불일치 상태(동시에 loading=true와 error!=null 등)를 원천 차단하려면 sealed 상태로 모델링해 보세요(Loading/Success/Empty/Error). 또는 현재 구조를 유지한다면 @immutable로 Compose 안정성을 높이는 것을 권장합니다.
예시(선호):
sealed interface PartListState { data object Loading : PartListState data class Success(val items: List<Part>) : PartListState data class Error(val message: String) : PartListState data object Empty : PartListState }대안(@immutable 부여):
+import androidx.compose.runtime.Immutable - data class PartListUiState( +@Immutable +data class PartListUiState( val partList: List<Part> = emptyList(), val partListLoading: Boolean = false, val partListError: String? = null )app/src/main/java/com/sampoom/android/feature/part/domain/model/CategoryList.kt (1)
3-11: 파생 속성은 보관보다 계산 프로퍼티가 안전합니다
totalCount/isEmpty를 생성 시 값으로 보관하면items가 외부에서 변경될 경우(읽기전용 List이더라도 실제 구현이 가변일 수 있음) 드리프트가 발생할 수 있습니다. 계산 프로퍼티로 전환하고, 방어적 복사로 불변성을 강화하는 것을 권장합니다. 또한 굳이Companion이름을 명시할 필요는 없습니다.data class CategoryList( - val items: List<Category>, - val totalCount: Int = items.size, - val isEmpty: Boolean = items.isEmpty() -) { - companion object Companion { - fun empty() = CategoryList(emptyList()) - } -} + val items: List<Category> +) { + val totalCount: Int get() = items.size + val isEmpty: Boolean get() = items.isEmpty() + + companion object { + fun empty() = CategoryList(emptyList()) + } +}추가로, 가능하면 생성자에서
items.toList()로 방어적 복사를 적용해 외부 변이로부터 보호하는 것도 고려해 주세요.app/src/main/java/com/sampoom/android/feature/part/domain/repository/PartRepository.kt (1)
7-10: ID 타입 명확화 및 계약 문서화 제안
Long대신 의미 있는 별칭을 두면 오용을 줄일 수 있습니다(예:typealias CategoryId = Long,typealias GroupId = Long).- 각 메서드의 페이징/정렬/에러 정책을 KDoc로 명시해 호출측 계약을 분명히 해 주세요.
app/src/main/java/com/sampoom/android/feature/part/ui/PartListScreen.kt (5)
115-118: LazyColumn에 안정 키 지정으로 성능/재사용 개선목록 항목에 고유 키를 부여해 스크롤/애니메이션/재조합 효율을 높이세요.
- items(uiState.partList) { part -> - PartListItemCard(part = part) - } + items( + items = uiState.partList, + key = { it.partId } + ) { part -> + PartListItemCard(part = part) + }
124-131: 상세 진입 UX: Card를 클릭 가능하게
chevron_right아이콘을 노출하지만 카드가 클릭 불가여서 사용자가 혼동할 수 있습니다. 기본 onClick을 추가해 향후 상세 화면 연계를 쉽게 하세요(호출부 변경 없이 디폴트 람다 제공).-private fun PartListItemCard( - part: Part -) { +private fun PartListItemCard( + part: Part, + onClick: () -> Unit = {} +) { Card( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), + onClick = onClick, colors = CardDefaults.cardColors(containerColor = backgroundCardColor()) ) {호출부는 유지 가능합니다(현재는
PartListItemCard(part = part)그대로 사용).Also applies to: 115-117
49-51: LaunchedEffect 키 안정화리소스 문자열 변화(로케일 전환) 때마다
bindLabel이 재호출됩니다. 의도가 1회 바인딩이라면LaunchedEffect(Unit)이 안전합니다.- LaunchedEffect(errorLabel) { + LaunchedEffect(Unit) { viewModel.bindLabel(errorLabel) }
87-91: 고정 높이(modifier.height(200.dp)) 제거 권장이미
Box(Modifier.fillMaxSize(), contentAlignment = Center)로 감싸므로 내부 컨텐츠에 고정 높이를 줄 필요가 없습니다. 다양한 화면 크기에서 레이아웃 제약을 피하려면 생략하세요.- ErrorContent( - onRetry = { viewModel.onEvent(PartListUiEvent.RetryPartList) }, - modifier = Modifier.height(200.dp) - ) + ErrorContent( + onRetry = { viewModel.onEvent(PartListUiEvent.RetryPartList) } + )- EmptyContent( - message = stringResource(R.string.part_empty_part), - modifier = Modifier.height(200.dp) - ) + EmptyContent( + message = stringResource(R.string.part_empty_part) + )Also applies to: 100-103
161-165: Icon 오버로드 변화에 안전하도록 인자명 명시Compose 버전에 따른
Icon오버로드 변화에 덜 취약하도록 파라미터명을 명시하세요.- Icon( - painterResource(R.drawable.chevron_right), - contentDescription = stringResource(R.string.common_detail), - tint = textSecondaryColor() - ) + Icon( + painter = painterResource(id = R.drawable.chevron_right), + contentDescription = stringResource(R.string.common_detail), + tint = textSecondaryColor() + )app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (1)
171-184: BottomNavigation 선택 상태는 hierarchy 기반 비교가 안전합니다.중첩 네비게이션 시
currentDestination?.route == item.route는 실패할 수 있습니다.hierarchy를 사용하세요.- selected = currentDestination?.route == item.route, + selected = currentDestination?.hierarchy?.any { it.route == item.route } == true,app/src/main/java/com/sampoom/android/feature/part/data/mapper/PartMappers.kt (1)
10-12: DTO nullability 확인 및 방어적 매핑 고려.현재 DTO 필드가 null 가능이면 NPE 위험이 있습니다. 스키마가 non-null이 보장되지 않는 한,
requireNotNull/기본값으로 방어하세요.예시:
-fun PartDto.toModel(): Part = Part(partId, code, name, quantity) +fun PartDto.toModel(): Part = Part( + partId = requireNotNull(partId) { "partId is null" }, + code = code.orEmpty(), + name = name.orEmpty(), + quantity = quantity ?: 0L +)DTO 정의의 nullability를 확인해 주세요(파일: CategoryDto.kt, GroupDto.kt, PartDto.kt).
app/src/main/java/com/sampoom/android/feature/part/ui/PartListViewModel.kt (2)
24-27: SavedState로 받은 agencyId가 미사용입니다. 시그니처/호출을 정리하세요.현재
agencyId를 읽지만 사용하지 않습니다. API가 agency를 요구한다면 유스케이스/리포지토리까지 전달하세요. 요구하지 않는다면 라우트와 SavedState에서 제거해 일관성을 맞추세요.라우트 정의(AppNavHost.kt Lines 104-115)와 맞춰야 합니다.
45-69: 유스케이스 인자 확장 대비 여지 남기기(agencyId 포함 시).API가 agencyId를 요구하도록 정리되면 아래 호출부도 갱신 필요합니다.
- runCatching { getPartUseCase(groupId) } + runCatching { getPartUseCase(/* agencyId = */ agencyId, groupId) }app/src/main/java/com/sampoom/android/feature/part/ui/PartUiState.kt (1)
15-19: 구조 적절. 카테고리/그룹의 로딩·에러 분리로 UI 처리 용이합니다.현재 상태 분리는 합리적이며 확장성 있습니다. 추후 검색어 등 필드를 추가할 때도 이 패턴 유지 추천합니다.
app/src/main/java/com/sampoom/android/feature/part/ui/PartViewModel.kt (1)
23-24: StateFlow 노출 방식 개선(읽기 전용 보장).외부엔 읽기 전용을 강제하는 관례대로 asStateFlow()로 노출해 주세요. 유지보수성과 의도 전달에 유리합니다.
import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow ... - val uiState: StateFlow<PartUiState> = _uiState + val uiState: StateFlow<PartUiState> = _uiState.asStateFlow()app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt (3)
229-241: 긴 그룹 목록은 LazyColumn으로 전환(성능/메모리).Column+verticalScroll은 아이템 전부를 측정/배치하여 목록이 길면 성능 저하가 큽니다. LazyColumn으로 변경하고 key를 지정하세요.
- else -> { - Column( - Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - uiState.groupList.forEach { group -> - PartItemCard( - group = group, - onClick = { onNavigatePartList(group) } - ) - } - } - } + else -> { + LazyColumn( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items( + items = uiState.groupList, + key = { it.id } + ) { group -> + PartItemCard( + group = group, + onClick = { onNavigatePartList(group) } + ) + } + } + }
33-34: 동일 이름의 로컬 Composable과 import 충돌(가독성 저하).파일 하단에 private fun PartItemCard(...)가 있어 import된 PartItemCard와 이름이 겹칩니다. 혼동 방지를 위해 import를 제거하세요.
-import com.sampoom.android.feature.part.ui.PartItemCard
78-85: 하드코딩 문자열의 다국어 리소스화."부품명으로 검색", "검색"은 stringResource로 분리하여 i18n을 보장하세요. 위 검색창 수정 diff에 포함했습니다.
app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt (1)
250-263: 컬러 헬퍼는 읽기 전용임을 명시하면 좋습니다.isSystemInDarkTheme() 조회만 수행하므로 @ReadOnlyComposable로 어노테이트하면 의도가 명확합니다.
-import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.runtime.Composable +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable @Composable -fun backgroundColor() = if (isSystemInDarkTheme()) BgBlack else BgWhite +@ReadOnlyComposable +fun backgroundColor() = if (isSystemInDarkTheme()) BgBlack else BgWhite @Composable -fun backgroundCardColor() = if (isSystemInDarkTheme()) BgCardBlack else BgCardWhite +@ReadOnlyComposable +fun backgroundCardColor() = if (isSystemInDarkTheme()) BgCardBlack else BgCardWhite @Composable -fun textColor() = if (isSystemInDarkTheme()) BgWhite else BgCardBlack +@ReadOnlyComposable +fun textColor() = if (isSystemInDarkTheme()) BgWhite else BgCardBlack @Composable -fun textSecondaryColor() = if (isSystemInDarkTheme()) Grey200 else Grey300 +@ReadOnlyComposable +fun textSecondaryColor() = if (isSystemInDarkTheme()) Grey200 else Grey300 @Composable -fun disableColor() = if (isSystemInDarkTheme()) Grey400 else Grey100 +@ReadOnlyComposable +fun disableColor() = if (isSystemInDarkTheme()) Grey400 else Grey100
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (44)
app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt(7 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/feature/auth/data/remote/api/AuthApi.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt(3 hunks)app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt(3 hunks)app/src/main/java/com/sampoom/android/feature/auth/ui/SignUpScreen.kt(2 hunks)app/src/main/java/com/sampoom/android/feature/part/data/mapper/PartMappers.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/data/remote/api/PartApi.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/CategoryDto.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/GroupDto.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/PartDto.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/data/repository/PartRepositoryImpl.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/domain/model/Category.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/domain/model/CategoryList.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/domain/model/Group.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/domain/model/GroupList.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/domain/model/Part.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/domain/repository/PartRepository.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetCategoryUseCase.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetGroupUseCase.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetPartUseCase.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/ui/PartListScreen.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/ui/PartListUiEvent.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/ui/PartListUiState.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/ui/PartListViewModel.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt(2 hunks)app/src/main/java/com/sampoom/android/feature/part/ui/PartUiEvent.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/ui/PartUiState.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/ui/PartViewModel.kt(1 hunks)app/src/main/res/drawable/body.xml(1 hunks)app/src/main/res/drawable/cart.xml(1 hunks)app/src/main/res/drawable/chassis.xml(1 hunks)app/src/main/res/drawable/chevron_right.xml(1 hunks)app/src/main/res/drawable/dashboard.xml(1 hunks)app/src/main/res/drawable/delivery.xml(1 hunks)app/src/main/res/drawable/electric.xml(1 hunks)app/src/main/res/drawable/engine.xml(1 hunks)app/src/main/res/drawable/orders.xml(1 hunks)app/src/main/res/drawable/parts.xml(1 hunks)app/src/main/res/drawable/search.xml(1 hunks)app/src/main/res/drawable/transmission.xml(1 hunks)app/src/main/res/drawable/trim.xml(1 hunks)app/src/main/res/values/strings.xml(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
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/feature/auth/ui/LoginScreen.kt (2)
app/src/main/java/com/sampoom/android/core/ui/component/CommonSnackBar.kt (2)
rememberCommonSnackBarHostState(36-37)ShowErrorSnackBar(39-61)app/src/main/java/com/sampoom/android/core/ui/component/CommonTextField.kt (1)
CommonTextField(29-138)
app/src/main/java/com/sampoom/android/feature/part/ui/PartListScreen.kt (3)
app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt (2)
ErrorContent(329-348)EmptyContent(350-361)app/src/main/java/com/sampoom/android/feature/part/domain/model/PartList.kt (1)
items(3-11)app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt (3)
backgroundCardColor(253-254)textColor(256-257)textSecondaryColor(259-260)
app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (2)
app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt (1)
PartScreen(36-248)app/src/main/java/com/sampoom/android/feature/part/ui/PartListScreen.kt (1)
PartListScreen(41-122)
app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt (1)
app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt (3)
textSecondaryColor(259-260)backgroundCardColor(253-254)textColor(256-257)
🔇 Additional comments (30)
app/src/main/java/com/sampoom/android/feature/part/domain/model/Category.kt (1)
3-7: 단순 모델 도입 LGTM도메인 계층의 얇은 data class로 적절합니다. 후속으로 CategoryDto → Category 매핑에서 필드명과 타입 일치 여부만 확인해 주세요.
app/src/main/java/com/sampoom/android/core/ui/theme/Theme.kt (2)
15-48: 컬러 토큰 확장 LGTMMaterial3 ColorScheme 전반(variant/containers/outline 등) 채워 넣은 점 좋습니다. Color.kt의 대비와 의미 매핑만 유지되는지 확인되면 충분합니다.
Also applies to: 53-86
92-92: 동적 컬러 기본값 변경(true → false) 확인됨 — 명시적 오버라이드 필요 검토앱 진입점(MainActivity.kt:17)에서
SampoomManagementTheme을 호출할 때dynamicColor파라미터를 명시적으로 전달하지 않고 있습니다. 기본값이false로 변경되었으므로 Android 12+ 기기에서 Monet 동적 컬러가 비활성화됩니다.동적 컬러 유지가 의도라면:
- MainActivity.kt:17에서
SampoomManagementTheme(dynamicColor = true)명시 필요app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/PartDto.kt (1)
4-7: 서버 API 응답 형식 확인 필요검증 결과, Gson이 직렬화 라이브러리로 사용되고 있으며 현재 PartDto에는
@SerializedName어노테이션이 없습니다. 다만 역직렬화 문제 여부는 서버가 실제로 보내는 JSON 필드명에 따라 결정됩니다:
- ✓ 서버가
partId,code,name,quantity를 보내면 → 현재 코드 정상 작동- ✗ 서버가
id,code,name,count를 보내면 → Gson 매핑 실패, 어노테이션 필수필수 확인 사항:
- 서버 API 문서에서 실제 JSON 필드명 확인
- API 응답 예시 또는 네트워크 로그에서 필드명 검증
- 이전 버전 PartDto와 비교해 필드명이 변경되었는지 확인
필드명이 서버 JSON과 불일치하면 review comment의 옵션 B 중 Gson
@SerializedName어노테이션 적용이 필요합니다.app/src/main/res/drawable/chevron_right.xml (1)
1-5: 벡터 드로어블 리소스가 올바르게 정의되었습니다.새로운 chevron_right 아이콘이 @color/text를 사용하여 테마 시스템과 일관성 있게 구현되었으며, autoMirrored 속성도 적절히 설정되어 RTL 레이아웃을 지원합니다.
app/src/main/res/drawable/dashboard.xml (1)
8-8: 색상 리소스 참조로의 전환이 적절합니다.하드코딩된 색상 값을 @color/text로 변경하여 중앙 집중식 테마 관리가 가능해졌습니다. 이는 다크 모드 지원 및 일관된 UI 스타일링에 유리합니다.
app/src/main/res/drawable/transmission.xml (1)
1-9: 새로운 transmission 아이콘이 올바르게 추가되었습니다.벡터 드로어블이 @color/text를 사용하여 프로젝트의 테마 시스템과 일관성 있게 구현되었습니다.
app/src/main/res/drawable/orders.xml (1)
8-8: 테마 색상 적용이 적절합니다.하드코딩된 색상을 색상 리소스로 전환하여 테마 일관성이 향상되었습니다.
app/src/main/res/drawable/parts.xml (1)
8-8: 색상 리소스 마이그레이션이 올바릅니다.@color/text 사용으로 테마 시스템과의 통합이 개선되었습니다.
app/src/main/res/drawable/cart.xml (1)
11-11: 색상 리소스 전환이 적절합니다.하드코딩된 색상 값을 @color/text로 변경하여 테마 관리 일관성이 향상되었습니다.
app/src/main/java/com/sampoom/android/feature/part/ui/PartUiEvent.kt (1)
5-10: UI 이벤트 인터페이스가 잘 설계되었습니다.sealed interface를 사용한 이벤트 정의가 적절하며, 데이터가 없는 이벤트는 object로, 데이터를 포함하는 이벤트는 data class로 구현하여 Kotlin의 best practice를 따르고 있습니다. 카테고리 로딩, 선택, 재시도 플로우를 명확하게 표현합니다.
app/src/main/res/drawable/delivery.xml (1)
11-11: 색상 리소스 중앙화가 잘 적용되었습니다.하드코딩된 색상 값을
@color/text리소스로 변경하여 테마 지원이 가능해졌습니다. 이는 라이트/다크 모드 및 동적 테마 지원에 적합한 접근 방식입니다.app/src/main/res/drawable/chassis.xml (1)
1-22: 새로운 섀시 아이콘이 잘 구현되었습니다.벡터 드로어블이 올바르게 정의되어 있으며, 중앙화된
@color/text리소스를 일관되게 사용하여 테마 지원이 적절히 적용되었습니다.app/src/main/res/drawable/electric.xml (1)
1-13: 전기 부품 아이콘이 적절하게 추가되었습니다.벡터 드로어블이 올바른 구조를 가지고 있으며, 테마 색상 리소스를 사용하여 일관된 UI를 제공합니다.
app/src/main/res/drawable/search.xml (1)
11-11: 검색 아이콘의 색상 리소스 마이그레이션이 완료되었습니다.
strokeColor를 중앙화된 색상 리소스로 변경하여 다른 아이콘들과 일관된 테마 적용이 가능합니다.app/src/main/res/drawable/engine.xml (1)
1-13: 엔진 부품 아이콘이 잘 구현되었습니다.복잡한 경로 데이터를 포함한 벡터 드로어블이 올바르게 정의되어 있으며, 중앙화된 색상 리소스를 사용하여 테마 일관성을 유지합니다.
app/src/main/res/drawable/trim.xml (1)
1-13: 트림 부품 아이콘이 적절하게 추가되었습니다.벡터 드로어블이 올바르게 구현되어 있으며, 중앙화된 색상 리소스를 사용합니다. 다른 아이콘들과 달리 너비가 26dp인 점은 아이콘의 가로세로 비율을 고려한 의도적인 설정으로 보입니다.
app/src/main/java/com/sampoom/android/feature/part/domain/model/Group.kt (1)
3-8: 도메인 모델이 적절하게 정의되었습니다.
Group데이터 클래스가 명확한 구조를 가지고 있으며, 필요한 속성들(id,code,name,categoryId)이 모두 포함되어 있습니다. 카테고리와의 관계를 나타내는categoryId가 적절히 포함되어 있습니다.app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/CategoryDto.kt (1)
3-7: 직렬화 어노테이션은 필요하지 않습니다.프로젝트의 Gson 설정을 확인한 결과,
NetworkModule.kt에서GsonBuilder가FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES로 구성되어 있습니다. 모든 기존 DTO(LoginRequestDto, LoginResponseDto, SignUpRequestDto, SignUpResponseDto)에서 어노테이션을 사용하지 않으며, CategoryDto도 이와 동일한 패턴을 따르고 있으므로 추가 어노테이션 없이 정상 작동합니다.app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetGroupUseCase.kt (1)
7-11: 구현이 깔끔합니다.Use Case 패턴이 올바르게 적용되었고, repository로의 단순 위임 구조가 적절합니다.
app/src/main/java/com/sampoom/android/feature/part/data/remote/dto/GroupDto.kt (1)
3-8: DTO 구조가 적절합니다.필드 타입과 명명이 명확하고, API 응답 매핑에 적합한 구조입니다.
app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt (2)
34-34: 좋은 개선입니다.
collectAsStateWithLifecycle()사용으로 lifecycle-aware한 상태 수집이 구현되었습니다.
58-127: 상태 참조가 일관되게 업데이트되었습니다.모든
state참조가uiState로 올바르게 변경되었고, 로직이 정상적으로 유지됩니다.app/src/main/java/com/sampoom/android/feature/part/domain/model/GroupList.kt (1)
3-11: 도메인 모델이 잘 설계되었습니다.파생 속성(
totalCount,isEmpty)이 적절하게 기본값으로 계산되고,empty()팩토리 메서드가 편리한 빈 상태 생성을 제공합니다.app/src/main/java/com/sampoom/android/feature/auth/ui/LoginViewModel.kt (1)
23-24: 네이밍 변경이 일관되게 적용되었습니다.
state에서uiState로의 리네이밍이 파일 전체에 걸쳐 일관되게 적용되었고, LoginScreen의 변경 사항과 잘 연동됩니다.app/src/main/java/com/sampoom/android/feature/part/domain/model/Part.kt (1)
4-8: 모델 구조가 개선되었습니다.필드명이 더 명확해졌고(
partId,quantity,code추가), 타입도Long으로 확장되어 더 큰 값을 처리할 수 있습니다. 계층적 카테고리->그룹->부품 구조와 잘 맞습니다.app/src/main/java/com/sampoom/android/feature/part/domain/usecase/GetPartUseCase.kt (1)
10-10: Use Case 시그니처가 계층적 구조를 반영합니다.
groupId파라미터 추가로 그룹별 부품 조회가 가능해졌고, 카테고리->그룹->부품의 계층적 흐름과 잘 맞습니다.app/src/main/java/com/sampoom/android/feature/part/data/repository/PartRepositoryImpl.kt (1)
26-30: API 경로의 하드코딩된1확인됨 - 설계 일관성 재검토 필요검증 결과:
- ✓
PartApi.kt라인 18의@GET("agency/1/group/{groupId}")- 하드코딩된1확인됨- ✓ 현재 메서드 시그니처들은 모두 일관됨 (모두
groupId만 사용)⚠️ 설계 패턴 불일치:getGroupList는agency/category/{categoryId}로 categoryId를 파라미터화하지만,getPartList는 agencyId를 하드코딩함현재 상태에서는
PartRepositoryImpl,GetPartUseCase,PartRepository간 시그니처 일관성은 유지되고 있습니다. 그러나 API 경로의 하드코딩된1은 다른 엔드포인트의 패턴과 맞지 않으므로, 설계 의도를 명확히 해야 합니다:
- agencyId가 정말 항상
1이어야 한다면, API 설계가 의도된 것인지 확인- 그렇지 않다면, agencyId를 파라미터로 추가하고 모든 계층의 시그니처를 업데이트해야 합니다
app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt (1)
42-47: 에러 라벨 바인딩 시점과 초기 로드 순서 확인 필요.ViewModel이 init에서 즉시 로드하면 라벨 바인딩 전 실패 시 공백 에러가 노출될 수 있습니다. ViewModel 측 폴백/가드 적용(제안 드린 diff) 후에도 문제가 남으면, 여기서 bindLabel 직후 카테고리 로드를 트리거하는 방식으로 조정해 주세요.
app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt (1)
223-263: 팔레트/토큰 추가 전반은 적절합니다.테마 확장과 토큰 구성(백그라운드/텍스트/메인 램프)이 일관적입니다. UI 변경에 활용하기 좋습니다.
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (4)
app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (2)
40-41: 라우트 네임스페이스 정비 완료 — 👍
ROUTE_PART_LIST에parts/접두어를 추가해 일관성과 충돌 위험을 줄였습니다.
96-99: agencyId 하드코딩(1)으로 다기관 환경에서 오동작현재 기관/세션 상태를 사용하도록 교체하세요. 최소한 변수로 치환 후 주입/조회로 연결 필요.
예시:
- onNavigatePartList = { group -> - navController.navigate(routePartList(1, group.id)) - } + onNavigatePartList = { group -> + val currentAgencyId = /* TODO: 세션/VM/DI에서 조회 */ + navController.navigate(routePartList(currentAgencyId, group.id)) + }app/src/main/java/com/sampoom/android/feature/part/ui/PartViewModel.kt (1)
73-84: 그룹 로드 경합/역전 방지 구현 반영 👍이전 로드 취소와 최신 선택 검사로 상태 오염을 잘 방지했습니다.
Also applies to: 86-109
app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt (1)
70-93: 검색창 비동작/컴파일 오류: 문자열 리터럴 제거검증 결과,
app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt의 72-73 라인에서 문제가 확인되었습니다.value와onValueChange파라미터가 문자열 리터럴로 감싸져 있어 동작하지 않으며, OutlinedTextField의 타입 요구사항과도 맞지 않습니다.필수 수정:
OutlinedTextField( - value = "uiState.searchQuery", - onValueChange = { "viewModel.onSearchQueryChanged(it)" }, + value = uiState.searchQuery, + onValueChange = { viewModel.onSearchQueryChanged(it) }, placeholder = { Text(stringResource(R.string.part_placeholder_search)) },유사한 패턴은 다른 파일에서 발견되지 않았습니다.
🧹 Nitpick comments (4)
app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt (4)
202-209: 스크롤 컨테이너 내fillMaxSize로딩 박스는 과도함세로 스크롤 내에서는 고정 높이(예: 200dp) 플레이스홀더가 UX에 더 적합합니다. 카테고리 섹션과 일관되게 맞추세요.
예:
- Box( - modifier = Modifier.fillMaxSize(), + Box( + modifier = Modifier + .fillMaxWidth() + .height(200.dp), contentAlignment = Alignment.Center ) {
255-256: 람다 래핑 제거
onClick = { onClick() }대신onClick = onClick이 간결합니다.- onClick = { onClick() }, + onClick = onClick,
318-320: 장식 아이콘의 contentDescription 제거우측 화살표는 장식용이므로
contentDescription = null이 접근성에 적합합니다(카드 전체가 클릭 대상).- Icon( - painterResource(R.drawable.chevron_right), - contentDescription = stringResource(R.string.common_detail) + Icon( + painterResource(R.drawable.chevron_right), + contentDescription = null )
283-292: 코드 매핑의 대소문자 견고성 확보서버/데이터 변경에 대비해
code.uppercase()기준으로 매핑하세요.-private fun resourceMapper(code: String): Int { - return when (code) { +private fun resourceMapper(code: String): Int { + return when (code.uppercase()) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt(6 hunks)app/src/main/java/com/sampoom/android/core/ui/component/EmptyContent.kt(1 hunks)app/src/main/java/com/sampoom/android/core/ui/component/ErrorContent.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/ui/PartListViewModel.kt(1 hunks)app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt(2 hunks)app/src/main/java/com/sampoom/android/feature/part/ui/PartViewModel.kt(1 hunks)app/src/main/res/values/strings.xml(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (3)
app/src/main/java/com/sampoom/android/feature/auth/ui/LoginScreen.kt (1)
LoginScreen(44-168)app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt (1)
PartScreen(32-244)app/src/main/java/com/sampoom/android/feature/part/ui/PartListScreen.kt (1)
PartListScreen(41-122)
app/src/main/java/com/sampoom/android/feature/part/ui/PartScreen.kt (3)
app/src/main/java/com/sampoom/android/core/ui/component/ErrorContent.kt (1)
ErrorContent(18-37)app/src/main/java/com/sampoom/android/core/ui/component/EmptyContent.kt (1)
EmptyContent(10-21)app/src/main/java/com/sampoom/android/core/ui/theme/Color.kt (3)
textSecondaryColor(259-260)backgroundCardColor(253-254)textColor(256-257)
🔇 Additional comments (3)
app/src/main/java/com/sampoom/android/app/navigation/AppNavHost.kt (1)
60-62: 임시 로그인 상태 하드코딩 제거 일정 확인
isLoggedIn = true는 데모 단계로 이해됩니다. Auth 저장소/세션 연동 시점 공유 부탁드립니다.app/src/main/res/values/strings.xml (2)
40-49: 부품 조회 화면 계층구조 문자열 일관성 확인 완료검증 결과,
part_title_category와part_title_group모두PartScreen.kt에서Text컴포저블의 제목 요소로 명확하게 사용되고 있습니다:
part_title_category(line 97): MaterialTheme.typography.titleMedium 스타일의 제목으로 표시part_title_group(line 194): 동일하게 제목 스타일로 표시문자열들이 카테고리 → 그룹 → 부품 계층 구조에 따라 일관되게 구성되어 있으며, 선택 프롬프트(
part_select_category,part_title_group)와 빈 상태(part_empty_category,part_empty_group,part_empty_part)가 각 단계마다 대칭적으로 제공되고 있습니다. 한글 표기와 명명 규칙도 일관성 있게 유지되어 있습니다.
40-55: 부품 조회 화면 문자열 리소스 추가 확인 완료검증 결과 모든 새로운 문자열 리소스가
PartScreen.kt와PartListScreen.kt에서 실제로 사용되고 있습니다. 제거된part_empty문자열도 코드 어디에서도 참조되지 않으므로 안전하게 제거되었습니다. 문자열 명명 규칙과 한글 표기도 일관되게 유지되고 있습니다.
📝 Summary
부품 조회화면 구현
🙏 Question & PR point
📬 Reference
Summary by CodeRabbit
새로운 기능
개선사항
버그 수정 / 안정성