Conversation
📝 WalkthroughWalkthrough상세 내역 페이지의 모집자(RecruiterDetail)와 참여자(ParticipantDetail) 화면을 새로 구현하고, 상태 표시 시스템을 개선하며, 입금 및 배송 정보 표시 UI 컴포넌트들을 추가했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant RecruiterDetailRoute
participant RecruiterViewModel
participant RecruiterDetailScreen
participant Modal/Navigation
User->>RecruiterDetailRoute: 이력 상세 화면 진입<br/>(recruitId 포함)
RecruiterDetailRoute->>RecruiterViewModel: 초기화 (SavedStateHandle)
RecruiterViewModel->>RecruiterViewModel: getRecruiterDetail(recruitId)
RecruiterViewModel->>RecruiterViewModel: uiState 업데이트<br/>(ApiState.Success)
RecruiterDetailRoute->>RecruiterDetailScreen: 데이터 전달
RecruiterDetailScreen->>RecruiterDetailScreen: PartyInfoSection 렌더링
RecruiterDetailScreen->>RecruiterDetailScreen: ProgressStatusSection 렌더링
RecruiterDetailScreen->>RecruiterDetailScreen: 참여자 목록 렌더링
User->>RecruiterDetailScreen: 입금/배송 액션 클릭
RecruiterDetailScreen->>RecruiterViewModel: intent 전달<br/>(SubmitDeposit/ConfirmDelivery)
RecruiterViewModel->>RecruiterViewModel: processIntent 처리
RecruiterViewModel->>Modal/Navigation: UiEffect 발생<br/>(NavigateToPartyDetail)
Modal/Navigation->>User: 모달 표시 또는 네비게이션
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes 새로운 피처로 도메인 모델 추가, 복잡한 ViewModel 상태 관리, 10개 이상의 새로운 Compose 컴포넌트, 네비게이션 시스템 개선, 모달 시스템 추가 등 여러 계층에 걸친 상당한 변경사항이 있어서입니다. Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/poti/android/presentation/history/component/HistoryStateLabel.kt`:
- Around line 81-113: getStateColor currently omits an explicit mapping for
StateLabelStage.RECRUIT to StateLabelStatus.WAIT, causing it to fall back to
defaultColor; add a branch in getStateColor for "StateLabelStage.RECRUIT to
StateLabelStatus.WAIT" that follows the same size-dependent pattern as
DEPOSIT/DELIVERY WAIT (use isLarge to choose colors.sementicRed when large,
otherwise defaultColor), referencing the existing isLarge and defaultColor
variables so behavior is consistent with other WAIT cases.
In
`@app/src/main/java/com/poti/android/presentation/history/component/ParticipantDetailDialogs.kt`:
- Line 16: recruiterProfileUrl가 nullable이 아니어서 partnerProfileUrl(String?)과
불일치합니다; ParticipantDetailDialogs.kt에서 선언된 recruiterProfileUrl를 String?으로 변경하고,
해당 값이 사용되는 곳(예: HistoryDeliveryReviewModal 호출부 및 DeliveryReview 관련 코드)에서
null-safe 처리(엘비스 연산자 또는 null 체크)를 추가해 프로필 URL이 없을 때도 안전하게 동작하도록 수정하세요.
In
`@app/src/main/java/com/poti/android/presentation/history/component/ParticipantManagementHeader.kt`:
- Around line 45-52: The Icon inside ParticipantManagementHeader is interactive
(uses noRippleClickable(onHeaderClick)) but has contentDescription = null,
breaking accessibility; replace the null with an appropriate localized or
literal description (e.g., use
stringResource(R.string.history_action_open_participant_management) or "참여자 관리
열기") so screen readers can announce the action; update the Icon's
contentDescription property and add the new string resource if using
stringResource.
In
`@app/src/main/java/com/poti/android/presentation/history/component/ShippingInfoSection.kt`:
- Around line 36-42: The Text currently concatenates info.recipient,
info.zipcode, info.address and info.phone without a clear separator; replace
that concatenation with a stringResource format (e.g.
"history_shipping_info_address_format") and pass info.recipient, info.zipcode,
info.address, info.phone as format arguments so address and phone are separated
(suggested format includes a newline between address and phone). Update the Text
in ShippingInfoSection (the Text composable using info.recipient, info.zipcode,
info.address, info.phone) to call stringResource(...) and ensure the new string
resource is added to strings.xml (name: history_shipping_info_address_format)
and necessary imports are included.
In `@app/src/main/java/com/poti/android/presentation/history/mapper/Mapper.kt`:
- Around line 126-138: ProgressInfo.toUiModel() currently embeds hardcoded
Korean guideText strings; change it to avoid Context access by returning a
string resource ID (e.g., add/return guideResId: Int) or only include the step
in ProgressUiModel and move string resolution into the UI layer; update
ProgressUiModel to hold an Int guideResId or just step, adjust
ProgressInfo.toUiModel() to set that ID (or only step), and update the UI
consumer to call context.getString(guideResId) or map step->string resource in
the View layer.
In
`@app/src/main/java/com/poti/android/presentation/history/navigation/HistoryNavigation.kt`:
- Around line 72-73: The ParticipantManage destination isn't passing recruitId
to ParticipantManageRoute; update the composable for
HistoryRoute.ParticipantManage to extract the recruitId parameter from the
NavBackStackEntry/route (like the pattern used in PartyDetailNavigation's
navigation<PartyDetailGraph> wrapper) and pass it into ParticipantManageRoute
(e.g., call ParticipantManageRoute(recruitId = extractedRecruitId, modifier =
Modifier.padding(paddingValues))). Ensure you parse the parameter as Long and
handle missing/invalid values consistently with other navigations.
- Around line 63-69: RecruiterDetailRoute currently passes an empty lambda for
onNavigateToMypageRecruit so the RecruiterDetailUiEffect.NavigateBack has no
effect; replace the empty lambda with the navController pop-back handler (e.g.,
use navController::popBackStack or the existing onPopBackStack helper) so that
RecruiterDetailRoute's onNavigateToMypageRecruit triggers a back navigation just
like HistoryListRoute does.
In
`@app/src/main/java/com/poti/android/presentation/history/recruiter/RecruiterDetailScreen.kt`:
- Around line 57-68: The code only handles ApiState.Success for
uiState.recruiterDetailState and leaves other states blank; update the when over
uiState.recruiterDetailState to handle ApiState.Loading (show a loading
indicator/composable) and ApiState.Failure (show an error view with retry/back
actions that call viewModel.processIntent with appropriate
RecruiterDetailUiIntent values), keeping the existing RecruiterDetailScreen
branch intact and reusing the same modifier and intent calls for interactions.
In
`@app/src/main/java/com/poti/android/presentation/history/recruiter/RecruiterViewModel.kt`:
- Around line 21-27: The init block in RecruiterViewModel currently treats an
invalid recruitId (-1L) by setting recruiterDetailState to ApiState.Loading;
change this to produce an error recovery path: when recruitId == -1L call
updateState to set recruiterDetailState = ApiState.Failure(...) with a clear
error message (or an appropriate Failure subtype) and/or invoke the existing
navigation/effect method (e.g., emit a navigation event or call the ViewModel's
effect emitter) so the UI can show an error or navigate back; modify the init
and/or create a helper like handleInvalidRecruitId() and ensure
getParticipantManageDetail is only called when recruitId is valid.
🧹 Nitpick comments (16)
app/src/main/java/com/poti/android/presentation/history/component/HistoryDepositBottomSheet.kt (2)
31-32: 입력 상태는 rememberSaveable로 보존하는 편이 안전합니다.
회전/프로세스 재생성 시 입력값이 초기화될 수 있어, 입력 필드는 rememberSaveable이 더 적합합니다.♻️ 제안 수정
+import androidx.compose.runtime.saveable.rememberSaveable ... - var depositor by remember { mutableStateOf("") } - var depositTime by remember { mutableStateOf("") } + var depositor by rememberSaveable { mutableStateOf("") } + var depositTime by rememberSaveable { mutableStateOf("") }
72-77: 하드코딩된 226.dp 패딩은 리소스/디멘전으로 치환해주세요.
디자인 시스템 Dimens 또는 적절한 spacing 리소스로 관리하는 편이 유지보수에 유리합니다. 코딩 가이드라인에 따라 확인했습니다.app/src/main/java/com/poti/android/domain/model/history/RecruiterDetail.kt (1)
5-11: participantCount가 리스트와 불일치할 여지가 있습니다.
외부에서 다른 값을 주입할 수 있으니, 필요 없다면 계산 프로퍼티로 전환하거나 일관성 체크를 고려해주세요.♻️ 방향 예시
-data class RecruiterDetail( - val recruitId: Long, - val partySummary: PartySummary, - val progressInfo: ProgressInfo, - val participantInfoList: List<ParticipantInfo>, - val participantCount: Int = participantInfoList.size, -) +data class RecruiterDetail( + val recruitId: Long, + val partySummary: PartySummary, + val progressInfo: ProgressInfo, + val participantInfoList: List<ParticipantInfo>, +) { + val participantCount: Int get() = participantInfoList.size +}app/src/main/java/com/poti/android/domain/model/history/ParticipantDetail.kt (1)
17-41: DepositDone의 빈 문자열 센티넬 사용은 혼동 여지가 있습니다.완료 상태에서
accountNumber/dueDate가 의미 없는데 빈 문자열로 유지되면 UI/로직에서 실제 값으로 오해될 수 있습니다.null로 명시하는 쪽을 권장드립니다.♻️ 개선 제안
sealed interface DepositStatus { - val accountNumber: String - val dueDate: String + val accountNumber: String? + val dueDate: String? data class DepositWait( override val accountNumber: String, override val dueDate: String, ) : DepositStatus data class DepositCheck( override val accountNumber: String, override val dueDate: String, ) : DepositStatus object DepositDone : DepositStatus { - override val accountNumber: String = "" - override val dueDate: String = "" + override val accountNumber: String? = null + override val dueDate: String? = null } }app/src/main/java/com/poti/android/presentation/history/component/HistoryParticipantOverview.kt (1)
29-36: Modifier 위치를 Compose 관례에 맞춰 정리하는 것을 권장드립니다.현재
modifier가 마지막에 있어 호출부 가독성이 떨어질 수 있습니다. 관례대로modifier를 첫 번째 optional 파라미터로 이동하는 쪽을 검토해 주세요.♻️ 개선 제안
fun HistoryParticipantOverview( + modifier: Modifier = Modifier, memberList: String, userInfo: String, deliveryMethod: String, price: Int, participantStageType: StateLabelStage, participantStatusType: StateLabelStatus, - modifier: Modifier = Modifier, )코딩 가이드라인에 따라, 프로젝트 내 Compose 파라미터 순서 규칙과의 일치 여부를 확인해 주세요.
app/src/main/java/com/poti/android/presentation/history/model/CommonUiModel.kt (1)
6-17: UI 모델에@Immutable적용을 검토해 주세요.상태로 사용된다면 안정성 힌트로 리컴포지션 비용을 줄일 수 있습니다.
♻️ 개선 제안
+import androidx.compose.runtime.Immutable import com.poti.android.presentation.history.component.StateLabelStage import com.poti.android.presentation.history.component.StateLabelStatus +@Immutable data class PartySummaryUiModel( val imageUrl: String, val artist: String, val title: String, val partyStage: StateLabelStage, val partyStatus: StateLabelStatus, ) +@Immutable data class ProgressUiModel( val guideText: String, val step: Int, )코딩 가이드라인에 따라, 안정성 힌트 적용을 검토해 주세요.
app/src/main/java/com/poti/android/presentation/history/component/ProgressStatusSection.kt (1)
58-68:getStepIndicatorDrawable함수의 가시성을 고려해 주세요.현재
internal또는private없이 public으로 노출되어 있습니다. 이 함수가 외부 모듈에서 사용될 필요가 없다면private또는internal로 제한하는 것을 권장드립니다.♻️ 제안된 수정 사항
`@DrawableRes` -fun getStepIndicatorDrawable(step: Int): Int { +private fun getStepIndicatorDrawable(step: Int): Int { return when (step) { 0 -> R.drawable.img_history_step_indicator_0 1 -> R.drawable.img_history_step_indicator_1 2 -> R.drawable.img_history_step_indicator_2 3 -> R.drawable.img_history_step_indicator_3 4 -> R.drawable.img_history_step_indicator_4 else -> R.drawable.img_history_step_indicator_0 } }app/src/main/java/com/poti/android/presentation/history/recruiter/model/RecruiterDetailUiModel.kt (1)
19-47: computed property의 매 접근 시 재계산에 대해 고려해 주세요.
stage와status가 computed property로 정의되어 있어, 프로퍼티 접근 시마다when표현식이 재평가됩니다. 현재 로직이 간단하여 성능 영향은 미미하지만, Compose에서 빈번하게 접근될 경우를 대비하여 생성자 파라미터로 전달하거나lazy초기화를 고려해 볼 수 있습니다.다만, 현재 구조가 코드 가독성과 일관성 측면에서 장점이 있으므로 선택적으로 검토해 주세요.
app/src/main/java/com/poti/android/presentation/history/component/HistoryDeliveryModal.kt (2)
32-46: Modifier 파라미터 추가를 고려해 주세요.Jetpack Compose 모범 사례에 따르면, 재사용 가능한 Composable 함수는
Modifier를 첫 번째 선택적 파라미터로 받아야 합니다. 외부에서 레이아웃 조정이 필요할 수 있으므로 Modifier 파라미터 추가를 권장드립니다.♻️ 제안된 수정 사항
`@Composable` fun HistoryDeliveryConfirmModal( onConfirm: () -> Unit, onDismiss: () -> Unit, + modifier: Modifier = Modifier, ) { PotiSmallModal( + modifier = modifier, onDismissRequest = onDismiss,
59-67: 별점이 0일 때 버튼 비활성화 상태 표시를 고려해 주세요.현재
currentRating이 0일 때onBtnClick이 무시되지만, 버튼에 시각적인 비활성화 상태가 표시되지 않습니다. 사용자가 버튼이 작동하지 않는다고 혼동할 수 있으므로,PotiLargeModal에서 버튼 비활성화 상태를 지원한다면 이를 활용하는 것을 권장드립니다.app/src/main/java/com/poti/android/presentation/history/recruiter/RecruiterViewModel.kt (1)
43-64: LGTM! 에러 처리가 잘 구현되어 있어요.
launchScope를 통한 코루틴 실행과onError콜백을 통한 예외 처리가 적절하게 구현되어 있습니다.BaseViewModel의 패턴을 잘 따르고 있네요. TODO 주석도 서버 연결 작업이 필요함을 명확히 표시하고 있어요.서버 연결을 위한 UseCase 주입 및 Repository 호출 코드를 작성해 드릴까요?
app/src/main/java/com/poti/android/presentation/history/component/ParticipantDetailDialogs.kt (2)
14-18: DeliveryReview 상태와 RecruiterInfoForReview 사이에 중복이 있어요.
DeliveryReview상태가recruiterName,recruiterProfileUrl,recruiterRating을 가지고 있지만, Line 55-70에서 실제로는recruiterInfo파라미터의 값을 사용하고 있어요. 이 중복을 제거하거나,DeliveryReview상태의 값을 직접 사용하도록 수정하는 것이 좋을 것 같습니다.♻️ 제안 1: DeliveryReview 상태의 값을 직접 사용
is ParticipantDetailModalState.DeliveryReview -> { HistoryDeliveryReviewModal( - partnerNickname = recruiterInfo.name, - partnerProfileUrl = recruiterInfo.profileUrl, - partnerRating = recruiterInfo.rating, + partnerNickname = modalState.recruiterName, + partnerProfileUrl = modalState.recruiterProfileUrl, + partnerRating = modalState.recruiterRating, onConfirm = { rating -> onDismiss() onReviewSubmit(rating)
21-31: Modifier 파라미터가 누락되어 있어요.Jetpack Compose 컨벤션에 따르면
Modifier는 Composable 함수의 첫 번째 선택적 파라미터로 받아야 합니다. 재사용성과 일관성을 위해modifier파라미터를 추가하는 것을 권장드려요. 코딩 가이드라인에 따른 사항이에요.♻️ 제안된 수정 사항
`@Composable` fun ParticipantDetailDialogs( modalState: ParticipantDetailModalState, recruiterInfo: RecruiterInfoForReview, onDismiss: () -> Unit, onDepositSubmit: (String, String) -> Unit, onDeliveryConfirm: () -> Unit, onNavigateToReview: () -> Unit, onReviewSubmit: (Int) -> Unit, onReviewSkip: () -> Unit, + modifier: Modifier = Modifier, ) {app/src/main/java/com/poti/android/presentation/history/component/HistoryParticipantDropdown.kt (1)
105-119: Crossfade에 label 파라미터 추가를 권장드려요.
Crossfade컴포저블에label파라미터를 추가하면 Layout Inspector에서 디버깅할 때 유용해요. Modern Android Development에서 권장하는 사항이에요.♻️ 제안된 수정 사항
Crossfade( targetState = expanded, + label = "DropdownIconCrossfade", ) { expand ->app/src/main/res/values/strings.xml (1)
150-150: 복수형 리소스(plurals) 사용을 고려해주세요.
history_participant_management_title에서%d를 사용하여 참여자 수를 표시하고 있는데, 한국어에서는 필요 없을 수 있지만 향후 다국어 지원을 고려한다면plurals리소스 사용을 권장드려요.app/src/main/java/com/poti/android/presentation/history/participant/model/ParticipantDetailUiModel.kt (1)
77-84: Compose 안정성을 위해@Immutable적용을 권장합니다.UI State로 쓰일 가능성이 높아 안정성 힌트를 주면 불필요한 리컴포지션을 줄일 수 있습니다. 또한
items가 불변 컬렉션이라면ImmutableList사용도 고려해 주세요.♻️ 예시 변경
+import androidx.compose.runtime.Immutable-data class DepositInfoUiModel( +@Immutable +data class DepositInfoUiModel(
| @Composable | ||
| private fun getStateColor( | ||
| stage: StateLabelStage, | ||
| status: StateLabelStatus, | ||
| size: StateLabelSize, | ||
| ): Color { | ||
| val isLarge = size == StateLabelSize.LARGE | ||
| val defaultColor = colors.gray700 | ||
|
|
||
| return when (stage to status) { | ||
| // DEPOSIT 단계 | ||
| StateLabelStage.DEPOSIT to StateLabelStatus.WAIT -> | ||
| if (isLarge) colors.sementicRed else defaultColor | ||
|
|
||
| StateLabelStage.DEPOSIT to StateLabelStatus.CHECK -> | ||
| if (isLarge) colors.poti600 else colors.sementicRed | ||
|
|
||
| StateLabelStage.DEPOSIT to StateLabelStatus.DONE -> | ||
| if (isLarge) defaultColor else colors.poti600 | ||
|
|
||
| // DELIVERY 단계 | ||
| StateLabelStage.DELIVERY to StateLabelStatus.WAIT -> | ||
| if (isLarge) colors.sementicRed else defaultColor | ||
|
|
||
| StateLabelStage.DELIVERY to StateLabelStatus.START -> colors.poti600 | ||
| StateLabelStage.DELIVERY to StateLabelStatus.DONE -> defaultColor | ||
|
|
||
| // RECRUIT 단계 | ||
| StateLabelStage.RECRUIT to StateLabelStatus.ING -> colors.sementicRed | ||
| StateLabelStage.RECRUIT to StateLabelStatus.DONE -> colors.poti600 | ||
|
|
||
| else -> defaultColor | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's read the full HistoryStateLabel.kt file to see the complete context
wc -l app/src/main/java/com/poti/android/presentation/history/component/HistoryStateLabel.ktRepository: team-poti/POTI-ANDROID
Length of output: 188
🏁 Script executed:
# Read the entire file to understand the context
cat -n app/src/main/java/com/poti/android/presentation/history/component/HistoryStateLabel.ktRepository: team-poti/POTI-ANDROID
Length of output: 6007
🏁 Script executed:
# Search for the enum definitions of StateLabelStage and StateLabelStatus
rg "enum class StateLabelStage|enum class StateLabelStatus" -A 10Repository: team-poti/POTI-ANDROID
Length of output: 2291
🏁 Script executed:
# Check if there are any tests or documentation that clarify the expected behavior
fd "StateLabelStage\|StateLabelStatus" -t f | head -20Repository: team-poti/POTI-ANDROID
Length of output: 48
🏁 Script executed:
# Search for places where RECRUIT is used with WAIT or where combinations are validated
rg "RECRUIT.*WAIT|WAIT.*RECRUIT" --type ktRepository: team-poti/POTI-ANDROID
Length of output: 92
🏁 Script executed:
# Check for API/data models that might define valid combinations
rg "StateLabelStage|StateLabelStatus" --type kt -l | xargs grep -l "repository\|usecase\|api" | head -10Repository: team-poti/POTI-ANDROID
Length of output: 92
🏁 Script executed:
# Search for any test files related to HistoryStateLabel
fd "HistoryStateLabel" -t fRepository: team-poti/POTI-ANDROID
Length of output: 152
🏁 Script executed:
# Check if there's any design documentation or comments about valid combinations
rg "RECRUIT.*status|stage.*WAIT" --type kt -B 3 -A 3 | head -40Repository: team-poti/POTI-ANDROID
Length of output: 92
🏁 Script executed:
# Search for RECRUIT and WAIT combinations in the codebase (without file type filter)
rg "RECRUIT.*WAIT|WAIT.*RECRUIT"Repository: team-poti/POTI-ANDROID
Length of output: 405
🏁 Script executed:
# Look for all usages of StateLabelStage.RECRUIT to understand where it's used
rg "StateLabelStage\.RECRUIT" -B 2 -A 2Repository: team-poti/POTI-ANDROID
Length of output: 6911
🏁 Script executed:
# Search for any constraints or validation on status combinations
rg "stage.*status|Stage.*Status" | head -30Repository: team-poti/POTI-ANDROID
Length of output: 4173
🏁 Script executed:
# Check if there are any data models or API responses that define valid combinations
fd "History|history" -t f | xargs grep -l "status\|stage" 2>/dev/null | head -10Repository: team-poti/POTI-ANDROID
Length of output: 667
RECRUIT + WAIT 색상 매핑이 누락되었습니다
해당 조합은 실제로 데이터 흐름(Mapper.kt)에서 생성되며 더미 데이터에서도 사용되고 있으나, 현재 코드에서는 명시적 매핑이 없어 기본 색상(gray700)으로 떨어집니다. DEPOSIT, DELIVERY의 WAIT 경우처럼 일관성 있게 명시 매핑을 추가해 주세요.
제안 수정
// RECRUIT 단계
+ StateLabelStage.RECRUIT to StateLabelStatus.WAIT ->
+ if (isLarge) colors.sementicRed else defaultColor
StateLabelStage.RECRUIT to StateLabelStatus.ING -> colors.sementicRed
StateLabelStage.RECRUIT to StateLabelStatus.DONE -> colors.poti600🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/component/HistoryStateLabel.kt`
around lines 81 - 113, getStateColor currently omits an explicit mapping for
StateLabelStage.RECRUIT to StateLabelStatus.WAIT, causing it to fall back to
defaultColor; add a branch in getStateColor for "StateLabelStage.RECRUIT to
StateLabelStatus.WAIT" that follows the same size-dependent pattern as
DEPOSIT/DELIVERY WAIT (use isLarge to choose colors.sementicRed when large,
otherwise defaultColor), referencing the existing isLarge and defaultColor
variables so behavior is consistent with other WAIT cases.
|
|
||
| data class DeliveryReview( | ||
| val recruiterName: String, | ||
| val recruiterProfileUrl: String, |
There was a problem hiding this comment.
recruiterProfileUrl 타입이 nullable이 아니에요.
HistoryDeliveryReviewModal의 partnerProfileUrl 파라미터는 String? 타입인데, DeliveryReview.recruiterProfileUrl은 String 타입입니다. 프로필 URL이 없는 경우를 처리하기 위해 nullable 타입으로 변경하는 것이 좋아요.
💡 제안된 수정 사항
data class DeliveryReview(
val recruiterName: String,
- val recruiterProfileUrl: String,
+ val recruiterProfileUrl: String?,
val recruiterRating: String,
) : ParticipantDetailModalState📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| val recruiterProfileUrl: String, | |
| val recruiterProfileUrl: String?, |
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/component/ParticipantDetailDialogs.kt`
at line 16, recruiterProfileUrl가 nullable이 아니어서 partnerProfileUrl(String?)과
불일치합니다; ParticipantDetailDialogs.kt에서 선언된 recruiterProfileUrl를 String?으로 변경하고,
해당 값이 사용되는 곳(예: HistoryDeliveryReviewModal 호출부 및 DeliveryReview 관련 코드)에서
null-safe 처리(엘비스 연산자 또는 null 체크)를 추가해 프로필 URL이 없을 때도 안전하게 동작하도록 수정하세요.
| Icon( | ||
| painter = painterResource(id = R.drawable.ic_arrow_right_lg), | ||
| contentDescription = null, | ||
| tint = PotiTheme.colors.gray700, | ||
| modifier = Modifier | ||
| .padding(10.dp) | ||
| .size(24.dp) | ||
| .noRippleClickable(onHeaderClick), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# 1. 전체 파일 확인
cat app/src/main/java/com/poti/android/presentation/history/component/ParticipantManagementHeader.ktRepository: team-poti/POTI-ANDROID
Length of output: 188
🏁 Script executed:
# 2. 관련 string 리소스 확인
find . -name "strings.xml" -type f | head -5 | xargs grep -l "history\|participant" 2>/dev/null | head -3Repository: team-poti/POTI-ANDROID
Length of output: 188
🏁 Script executed:
# 3. 다른 Icon components에서 contentDescription 사용 패턴 확인
rg "Icon\(" --type kt -A 4 | grep -B 2 -A 2 "contentDescription" | head -30Repository: team-poti/POTI-ANDROID
Length of output: 92
🏁 Script executed:
# 다른 파일 형식으로 시도
find . -name "*.kt" 2>/dev/null | head -20Repository: team-poti/POTI-ANDROID
Length of output: 1632
🏁 Script executed:
# strings.xml 파일 찾기
find . -name "strings.xml" -o -name "strings-*.xml" 2>/dev/null | head -10Repository: team-poti/POTI-ANDROID
Length of output: 103
🏁 Script executed:
# 전체 Icon 패턴 검색 (generic)
grep -r "contentDescription" --include="*.kt" 2>/dev/null | grep -i "arrow\|header" | head -10Repository: team-poti/POTI-ANDROID
Length of output: 339
🏁 Script executed:
# ParticipantManagementHeader.kt 전체 파일 확인
cat app/src/main/java/com/poti/android/presentation/history/component/ParticipantManagementHeader.ktRepository: team-poti/POTI-ANDROID
Length of output: 2795
🏁 Script executed:
# strings.xml에서 history 및 participant 관련 문자열 확인
grep -i "history\|participant" app/src/main/res/values/strings.xml | head -20Repository: team-poti/POTI-ANDROID
Length of output: 1341
🏁 Script executed:
# PotiHeaderPrimary.kt 확인 - interactive인지 비교
cat app/src/main/java/com/poti/android/core/designsystem/component/navigation/PotiHeaderPrimary.kt | head -100Repository: team-poti/POTI-ANDROID
Length of output: 3047
🏁 Script executed:
# 다른 interactive Icon들의 contentDescription 사용 패턴 확인
grep -B 3 -A 3 "noRippleClickable\|clickable" app/src/main/java/com/poti/android/presentation/history/component/*.kt | grep -B 5 -A 5 "Icon(" | head -40Repository: team-poti/POTI-ANDROID
Length of output: 48
🏁 Script executed:
# 다른 interactive icon들의 contentDescription 패턴 확인
grep -r "noRippleClickable\|clickable" app/src/main/java/com/poti/android/presentation/ --include="*.kt" -B 5 | grep -A 10 "Icon(" | head -50Repository: team-poti/POTI-ANDROID
Length of output: 1445
🏁 Script executed:
# designsystem에서 accessible icon button 패턴 확인
grep -r "contentDescription" app/src/main/java/com/poti/android/core/designsystem/ --include="*.kt" -B 2 -A 1 | grep -v "null" | head -30Repository: team-poti/POTI-ANDROID
Length of output: 3178
🏁 Script executed:
# history 관련 모든 string resource 다시 확인
grep -i "action\|desc\|manage" app/src/main/res/values/strings.xmlRepository: team-poti/POTI-ANDROID
Length of output: 787
클릭 가능한 아이콘에 contentDescription이 필요합니다.
이 아이콘은 noRippleClickable(onHeaderClick)로 바인딩되어 있는 상호작용 요소입니다. 따라서 코딩 가이드라인의 "장식용 이미지 예외"는 적용되지 않습니다. 현재 contentDescription = null이면 스크린리더에서 이 클릭 액션을 식별할 수 없어 접근성이 차단됩니다.
stringResource()로 적절한 설명 문자열을 제공하거나, 간단한 문자열을 직접 추가하세요. 예: contentDescription = "참여자 관리 열기" 또는 stringResource(R.string.history_action_open_participant_management)
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/component/ParticipantManagementHeader.kt`
around lines 45 - 52, The Icon inside ParticipantManagementHeader is interactive
(uses noRippleClickable(onHeaderClick)) but has contentDescription = null,
breaking accessibility; replace the null with an appropriate localized or
literal description (e.g., use
stringResource(R.string.history_action_open_participant_management) or "참여자 관리
열기") so screen readers can announce the action; update the Icon's
contentDescription property and add the new string resource if using
stringResource.
| Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { | ||
| Text( | ||
| text = "${info.recipient}\n(${info.zipcode}) ${info.address}${info.phone}", | ||
| style = PotiTheme.typography.body14m, | ||
| color = PotiTheme.colors.black, | ||
| lineHeight = PotiTheme.typography.body14m.fontSize * 1.5, | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd "ShippingInfoSection.kt" --type fRepository: team-poti/POTI-ANDROID
Length of output: 154
🏁 Script executed:
cat -n app/src/main/java/com/poti/android/presentation/history/component/ShippingInfoSection.ktRepository: team-poti/POTI-ANDROID
Length of output: 2982
🏁 Script executed:
fd "strings.xml" --type f | head -5Repository: team-poti/POTI-ANDROID
Length of output: 101
🏁 Script executed:
rg "info\.address.*info\.phone|info\.phone.*info\.address" --type kt -A 2 -B 2Repository: team-poti/POTI-ANDROID
Length of output: 92
🏁 Script executed:
cat app/src/main/res/values/strings.xml | head -100Repository: team-poti/POTI-ANDROID
Length of output: 5159
🏁 Script executed:
rg "history_shipping" app/src/main/res/values/ -A 1 -B 1Repository: team-poti/POTI-ANDROID
Length of output: 365
🏁 Script executed:
rg "shippping_info\|shipping_info\|address_format\|phone" app/src/main/res/values/strings.xmlRepository: team-poti/POTI-ANDROID
Length of output: 48
주소와 전화번호 사이에 구분자 추가하고 문자열 리소스로 분리해주세요.
현재 코드에서 주소와 전화번호가 구분자 없이 붙어 표시되고 있어요. 구분자를 추가하고 i18n 지원을 위해 stringResource()로 리소스화하는 게 좋겠습니다.
제안 수정
- text = "${info.recipient}\n(${info.zipcode}) ${info.address}${info.phone}",
+ text = stringResource(
+ id = R.string.history_shipping_info_address_format,
+ info.recipient,
+ info.zipcode,
+ info.address,
+ info.phone,
+ ),strings.xml에 추가:
<string name="history_shipping_info_address_format">%1$s\n(%2$s) %3$s\n%4$s</string>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { | |
| Text( | |
| text = "${info.recipient}\n(${info.zipcode}) ${info.address}${info.phone}", | |
| style = PotiTheme.typography.body14m, | |
| color = PotiTheme.colors.black, | |
| lineHeight = PotiTheme.typography.body14m.fontSize * 1.5, | |
| ) | |
| Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { | |
| Text( | |
| text = stringResource( | |
| id = R.string.history_shipping_info_address_format, | |
| info.recipient, | |
| info.zipcode, | |
| info.address, | |
| info.phone, | |
| ), | |
| style = PotiTheme.typography.body14m, | |
| color = PotiTheme.colors.black, | |
| lineHeight = PotiTheme.typography.body14m.fontSize * 1.5, | |
| ) |
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/component/ShippingInfoSection.kt`
around lines 36 - 42, The Text currently concatenates info.recipient,
info.zipcode, info.address and info.phone without a clear separator; replace
that concatenation with a stringResource format (e.g.
"history_shipping_info_address_format") and pass info.recipient, info.zipcode,
info.address, info.phone as format arguments so address and phone are separated
(suggested format includes a newline between address and phone). Update the Text
in ShippingInfoSection (the Text composable using info.recipient, info.zipcode,
info.address, info.phone) to call stringResource(...) and ensure the new string
resource is added to strings.xml (name: history_shipping_info_address_format)
and necessary imports are included.
| fun ProgressInfo.toUiModel(): ProgressUiModel { | ||
| val guideText = when (step) { | ||
| 0 -> "모집이 시작되었습니다." | ||
| 1 -> "입금을 확인하고 있습니다." | ||
| 2 -> "배송이 시작되었습니다." | ||
| 3 -> "거래가 완료되었습니다." | ||
| else -> "" | ||
| } | ||
| return ProgressUiModel( | ||
| guideText = guideText, | ||
| step = step, | ||
| ) | ||
| } |
There was a problem hiding this comment.
하드코딩된 문자열을 리소스로 분리해주세요.
ProgressInfo.toUiModel()에서 guideText가 한국어 문자열로 하드코딩되어 있어요. 코딩 가이드라인에 따르면 하드코딩된 문자열 대신 strings.xml 리소스를 사용해야 합니다. Mapper가 presentation 레이어에 있으므로 Context 접근이 어려울 수 있는데, 이 경우 문자열 리소스 ID를 반환하거나 ProgressUiModel에서 step만 저장하고 UI에서 문자열을 결정하도록 하는 방법을 고려해주세요.
💡 제안된 수정 사항 - 리소스 ID 반환 방식
fun ProgressInfo.toUiModel(): ProgressUiModel {
- val guideText = when (step) {
- 0 -> "모집이 시작되었습니다."
- 1 -> "입금을 확인하고 있습니다."
- 2 -> "배송이 시작되었습니다."
- 3 -> "거래가 완료되었습니다."
- else -> ""
+ val guideTextResId = when (step) {
+ 0 -> R.string.history_state_guide_recruit_started
+ 1 -> R.string.history_state_guide_deposit_checking
+ 2 -> R.string.history_state_guide_delivery_started
+ 3 -> R.string.history_state_guide_transaction_complete
+ else -> null
}
return ProgressUiModel(
- guideText = guideText,
+ guideTextResId = guideTextResId,
step = step,
)
}🤖 Prompt for AI Agents
In `@app/src/main/java/com/poti/android/presentation/history/mapper/Mapper.kt`
around lines 126 - 138, ProgressInfo.toUiModel() currently embeds hardcoded
Korean guideText strings; change it to avoid Context access by returning a
string resource ID (e.g., add/return guideResId: Int) or only include the step
in ProgressUiModel and move string resolution into the UI layer; update
ProgressUiModel to hold an Int guideResId or just step, adjust
ProgressInfo.toUiModel() to set that ID (or only step), and update the UI
consumer to call context.getString(guideResId) or map step->string resource in
the View layer.
app/src/main/java/com/poti/android/presentation/history/navigation/HistoryNavigation.kt
Show resolved
Hide resolved
| composable<HistoryRoute.ParticipantManage> { | ||
| ParticipantManageRoute(modifier = Modifier.padding(paddingValues)) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check the structure and view the HistoryNavigation.kt file
git ls-files app/src/main/java/com/poti/android/presentation/history/navigation/Repository: team-poti/POTI-ANDROID
Length of output: 153
🏁 Script executed:
# Read the HistoryNavigation.kt file
cat -n app/src/main/java/com/poti/android/presentation/history/navigation/HistoryNavigation.ktRepository: team-poti/POTI-ANDROID
Length of output: 3208
🏁 Script executed:
# Check ParticipantManageRoute function signature
rg -n 'fun\s+ParticipantManageRoute' app/src/main/java/com/poti/android/presentation/history/recruiter/ -A5Repository: team-poti/POTI-ANDROID
Length of output: 831
🏁 Script executed:
# Also check if there's a way to extract route arguments in the composable block
rg -n 'composable<HistoryRoute' app/src/main/java/com/poti/android/presentation/history/navigation/ -B2 -A8Repository: team-poti/POTI-ANDROID
Length of output: 3587
🏁 Script executed:
# Check ParticipantManageScreen to see if it uses recruitId from anywhere
cat -n app/src/main/java/com/poti/android/presentation/history/recruiter/ParticipantManageScreen.ktRepository: team-poti/POTI-ANDROID
Length of output: 500
🏁 Script executed:
# Check if there's a ViewModel or if recruitId is supposed to come from somewhere
rg -n 'ParticipantManage' app/src/main/java/com/poti/android/presentation/history/recruiter/ -A3 -B1Repository: team-poti/POTI-ANDROID
Length of output: 11679
🏁 Script executed:
# Check how other routes with parameters handle extraction (if any exist in codebase)
rg -n 'composable<.*\(' app/src/main/java/com/poti/android/presentation/ -A3 | grep -E '(composable|data class|val )' | head -30Repository: team-poti/POTI-ANDROID
Length of output: 48
🏁 Script executed:
# Check Android Navigation Compose documentation pattern or examples in codebase
rg -n 'composable.*NavBackStackEntry' app/src/main/java/com/poti/android/ -B2 -A3Repository: team-poti/POTI-ANDROID
Length of output: 48
🏁 Script executed:
# Search for patterns of accessing route parameters in composable blocks
rg -n 'it\.' app/src/main/java/com/poti/android/presentation/ --type=kt | grep -i 'composable\|route' | head -20Repository: team-poti/POTI-ANDROID
Length of output: 92
🏁 Script executed:
# Check if there's documentation or examples of typed navigation with parameters
rg -n 'it\.savedStateHandle\|NavBackStackEntry' app/src/main/java/com/poti/android/ --type=kt | head -20Repository: team-poti/POTI-ANDROID
Length of output: 92
🏁 Script executed:
# Search for how route parameters are accessed in composable blocks
rg -n 'composable<' app/src/main/java/com/poti/android/presentation/ -A5 | head -50Repository: team-poti/POTI-ANDROID
Length of output: 6185
🏁 Script executed:
# Check if NavBackStackEntry or similar is used anywhere
rg -n 'NavBackStackEntry\|savedStateHandle' app/src/main/java/com/poti/android/presentation/ -B2 -A3Repository: team-poti/POTI-ANDROID
Length of output: 48
🏁 Script executed:
# Check PartyDetailRoute to see if Detail has parameters
rg -n 'PartyDetailRoute\.' app/src/main/java/com/poti/android/presentation/party/detail/navigation/ -B5 -A2 | head -40Repository: team-poti/POTI-ANDROID
Length of output: 3446
🏁 Script executed:
# Check how the entry parameter is used to extract route info
rg -n 'entry\.' app/src/main/java/com/poti/android/presentation/party/detail/navigation/PartyDetailNavigation.kt -B2 -A2Repository: team-poti/POTI-ANDROID
Length of output: 634
🏁 Script executed:
# Check PartyDetailRoute sealed interface definition
rg -n 'sealed.*PartyDetailRoute\|data class.*Detail\|data object.*Detail' app/src/main/java/com/poti/android/presentation/party/detail/navigation/ -B2 -A5Repository: team-poti/POTI-ANDROID
Length of output: 48
🏁 Script executed:
# Look at PartyDetailGraph to understand the navigation structure
rg -n 'PartyDetailGraph\|class PartyDetailRoute' app/src/main/java/com/poti/android/presentation/party/detail/navigation/ -B3 -A5Repository: team-poti/POTI-ANDROID
Length of output: 48
🏁 Script executed:
# Check the full PartyDetailNavigation.kt file
cat -n app/src/main/java/com/poti/android/presentation/party/detail/navigation/PartyDetailNavigation.ktRepository: team-poti/POTI-ANDROID
Length of output: 2935
🏁 Script executed:
# Check if there's a ViewModel for ParticipantManageScreen that should receive recruitId
rg -n 'ParticipantManageViewModel\|class.*ParticipantManage.*ViewModel' app/src/main/java/com/poti/android/presentation/history/recruiter/ -B2 -A5Repository: team-poti/POTI-ANDROID
Length of output: 48
🏁 Script executed:
# Search for how recruitId might be accessed in this flow
rg -n 'recruitId' app/src/main/java/com/poti/android/presentation/history/ -B2 -A2Repository: team-poti/POTI-ANDROID
Length of output: 17586
ParticipantManage 목적지에서 recruitId를 추출하여 전달해야 합니다.
HistoryRoute.ParticipantManage는 recruitId: Long 파라미터를 가지고 있으며, 네비게이션 함수에서 올바르게 전달되고 있습니다. 그러나 composable 블록의 72-73줄에서 route 파라미터를 추출하지 않고 있어, ParticipantManageRoute에 recruitId가 전달되지 않습니다.
PartyDetailNavigation의 패턴(navigation<PartyDetailGraph> 래퍼 사용)을 참고하여 route 파라미터에 접근하거나, ParticipantManageRoute가 recruitId를 받을 수 있도록 수정이 필요합니다.
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/navigation/HistoryNavigation.kt`
around lines 72 - 73, The ParticipantManage destination isn't passing recruitId
to ParticipantManageRoute; update the composable for
HistoryRoute.ParticipantManage to extract the recruitId parameter from the
NavBackStackEntry/route (like the pattern used in PartyDetailNavigation's
navigation<PartyDetailGraph> wrapper) and pass it into ParticipantManageRoute
(e.g., call ParticipantManageRoute(recruitId = extractedRecruitId, modifier =
Modifier.padding(paddingValues))). Ensure you parse the parameter as Long and
handle missing/invalid values consistently with other navigations.
| when (val state = uiState.recruiterDetailState) { | ||
| is ApiState.Success -> { | ||
| RecruiterDetailScreen( | ||
| modifier = modifier, | ||
| detail = state.data, | ||
| onBackClick = { viewModel.processIntent(RecruiterDetailUiIntent.BackButtonClicked) }, | ||
| onDetailClick = { viewModel.processIntent(RecruiterDetailUiIntent.PartyCardClicked) }, | ||
| onParticipantManageDetailClick = { viewModel.processIntent(RecruiterDetailUiIntent.ParticipantSectionClicked) }, | ||
| ) | ||
| } | ||
| else -> {} | ||
| } |
There was a problem hiding this comment.
Loading 및 Error 상태에 대한 UI 처리가 필요해요.
현재 ApiState.Success만 처리하고 있고 나머지 상태(Loading, Failure)에서는 아무것도 표시하지 않아요. 사용자 경험을 위해 로딩 인디케이터나 에러 메시지를 표시하는 것이 좋습니다.
💡 제안된 수정 사항
when (val state = uiState.recruiterDetailState) {
is ApiState.Success -> {
RecruiterDetailScreen(
modifier = modifier,
detail = state.data,
onBackClick = { viewModel.processIntent(RecruiterDetailUiIntent.BackButtonClicked) },
onDetailClick = { viewModel.processIntent(RecruiterDetailUiIntent.PartyCardClicked) },
onParticipantManageDetailClick = { viewModel.processIntent(RecruiterDetailUiIntent.ParticipantSectionClicked) },
)
}
- else -> {}
+ is ApiState.Loading -> {
+ // 로딩 UI 표시
+ Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ CircularProgressIndicator()
+ }
+ }
+ is ApiState.Failure -> {
+ // 에러 UI 표시
+ PotiEmptyStateInline(text = state.message)
+ }
}🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/recruiter/RecruiterDetailScreen.kt`
around lines 57 - 68, The code only handles ApiState.Success for
uiState.recruiterDetailState and leaves other states blank; update the when over
uiState.recruiterDetailState to handle ApiState.Loading (show a loading
indicator/composable) and ApiState.Failure (show an error view with retry/back
actions that call viewModel.processIntent with appropriate
RecruiterDetailUiIntent values), keeping the existing RecruiterDetailScreen
branch intact and reusing the same modifier and intent calls for interactions.
| init { | ||
| if (recruitId != -1L) { | ||
| getParticipantManageDetail(recruitId) | ||
| } else { | ||
| updateState { copy(recruiterDetailState = ApiState.Loading) } | ||
| } | ||
| } |
There was a problem hiding this comment.
recruitId가 유효하지 않을 때의 에러 처리가 필요해요.
recruitId가 -1L인 경우 ApiState.Loading 상태로 설정되지만, 사용자에게 에러 상태를 표시하거나 이전 화면으로 돌아가는 등의 복구 메커니즘이 없습니다. 유효하지 않은 ID는 ApiState.Failure로 처리하거나 네비게이션 효과를 발생시키는 것이 더 적절할 수 있어요.
💡 제안된 수정 사항
init {
if (recruitId != -1L) {
getParticipantManageDetail(recruitId)
} else {
- updateState { copy(recruiterDetailState = ApiState.Loading) }
+ updateState { copy(recruiterDetailState = ApiState.Failure("Invalid recruit ID")) }
+ sendEffect(RecruiterDetailUiEffect.NavigateBack)
}
}🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/recruiter/RecruiterViewModel.kt`
around lines 21 - 27, The init block in RecruiterViewModel currently treats an
invalid recruitId (-1L) by setting recruiterDetailState to ApiState.Loading;
change this to produce an error recovery path: when recruitId == -1L call
updateState to set recruiterDetailState = ApiState.Failure(...) with a clear
error message (or an appropriate Failure subtype) and/or invoke the existing
navigation/effect method (e.g., emit a navigation event or call the ViewModel's
effect emitter) so the UI can show an error or navigate back; modify the init
and/or create a helper like handleInvalidRecruitId() and ensure
getParticipantManageDetail is only called when recruitId is valid.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/poti/android/presentation/history/component/HistoryDeliveryModal.kt`:
- Line 64: The confirm button handler currently ignores clicks when
currentRating == 0 (onBtnClick = { if (currentRating != 0)
onConfirm(currentRating) }), causing no feedback; update HistoryDeliveryModal so
the button either becomes visually disabled via its enabled/disabled prop when
currentRating == 0 and only calls onConfirm(currentRating) when >0, or keep it
enabled but show a user-facing feedback (e.g., Toast/Snackbar) inside the
onBtnClick branch when currentRating == 0; modify the onBtnClick handler and the
button's enabled/visual state accordingly and reference currentRating,
onBtnClick and onConfirm in your changes.
In
`@app/src/main/java/com/poti/android/presentation/history/component/ParticipantDetailDialogs.kt`:
- Around line 74-78: RecruiterInfoForReview의 profileUrl을 nullable로 변경하세요: data
class RecruiterInfoForReview(val name: String, val profileUrl: String?)로 타입을
수정하고 RecruiterInfoForReview를 생성하는 모든 지점과 이를 사용하는 코드(예:
HistoryDeliveryReviewModal의 recruiterProfileUrl 전달부)를 찾아 nullable을 처리하도록
업데이트하세요; null일 때의 UI/로직(프로필 이미지 로딩, 인텐트/링크 열기 등)은 안전 호출이나 기본값(또는 분기 처리)으로 대비하도록
변경하세요.
- Around line 55-70: ParticipantDetailModalState.DeliveryReview fields are
defined but not used—HistoryDeliveryReviewModal is fed recruiterInfo instead—so
either read the modalState values directly or simplify DeliveryReview to only
carry recruiterInfo; to fix, modify the is
ParticipantDetailModalState.DeliveryReview branch to pass
modalState.recruiterName, modalState.recruiterProfileUrl,
modalState.recruiterRating into HistoryDeliveryReviewModal (instead of
recruiterInfo.*) or refactor ParticipantDetailModalState.DeliveryReview into a
data object wrapping recruiterInfo and keep using recruiterInfo everywhere;
update usages of ParticipantDetailModalState.DeliveryReview and the
constructor/params of HistoryDeliveryReviewModal accordingly.
♻️ Duplicate comments (1)
app/src/main/java/com/poti/android/presentation/history/component/ParticipantDetailDialogs.kt (1)
7-19: Sealed interface 구조가 잘 설계되어 있어요.각 모달 상태를 명확하게 분리하여 타입 안전성을 확보했습니다. 다만, 이전 리뷰에서 지적된 것처럼 Line 16의
recruiterProfileUrl이String으로 선언되어 있지만,HistoryDeliveryReviewModal의 파라미터는String?을 기대하고 있어요. Nullable 타입으로 변경하시는 것을 권장드립니다.
🧹 Nitpick comments (2)
app/src/main/java/com/poti/android/presentation/history/component/HistoryDeliveryModal.kt (1)
48-56: Modifier 파라미터 추가를 권장드려요.코딩 가이드라인에 따르면,
Modifier는 항상 컴포저블 함수의 첫 번째 선택적 파라미터로 받아야 해요. 재사용성과 외부에서의 커스터마이징을 위해 추가하시는 것이 좋습니다.♻️ 제안된 수정 사항
`@Composable` fun HistoryDeliveryReviewModal( recruiterName: String, recruiterProfileUrl: String?, partnerRating: String, onConfirm: (Int) -> Unit, onSkip: () -> Unit, onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, ) {Based on coding guidelines, Modifier는 항상 컴포저블 함수의 첫 번째 선택적 파라미터로 받아야 합니다.
app/src/main/java/com/poti/android/presentation/history/component/ParticipantDetailDialogs.kt (1)
114-129: 프리뷰에서 데이터 중복이 보여요.
DeliveryReview프리뷰에서 동일한 데이터("포티","","4.8")가modalState와recruiterInfo양쪽에 전달되고 있어요. 앞서 언급한 리팩토링을 적용하면 이 중복도 해소될 것입니다.
| title = stringResource(R.string.history_delivery_review_modal_title), | ||
| text = stringResource(R.string.history_delivery_review_modal_text), | ||
| btnText = stringResource(R.string.history_delivery_review_modal_button), | ||
| onBtnClick = { if (currentRating != 0) onConfirm(currentRating) }, |
There was a problem hiding this comment.
별점 미선택 시 버튼 클릭에 대한 피드백이 없어요.
현재 currentRating이 0일 때 버튼을 클릭해도 아무 동작이 일어나지 않아 사용자가 혼란스러울 수 있어요. 버튼 비활성화 상태를 표시하거나, 토스트 메시지로 안내하는 방법을 고려해 보세요.
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/component/HistoryDeliveryModal.kt`
at line 64, The confirm button handler currently ignores clicks when
currentRating == 0 (onBtnClick = { if (currentRating != 0)
onConfirm(currentRating) }), causing no feedback; update HistoryDeliveryModal so
the button either becomes visually disabled via its enabled/disabled prop when
currentRating == 0 and only calls onConfirm(currentRating) when >0, or keep it
enabled but show a user-facing feedback (e.g., Toast/Snackbar) inside the
onBtnClick branch when currentRating == 0; modify the onBtnClick handler and the
button's enabled/visual state accordingly and reference currentRating,
onBtnClick and onConfirm in your changes.
There was a problem hiding this comment.
@PBEM22, 😄 재치있는 반응 감사드려요! 토스트(음식)도 좋지만, 필요하시면 나중에 토스트 메시지 관련해서도 언제든 말씀해 주세요!
🐰✨
app/src/main/java/com/poti/android/presentation/history/component/ParticipantDetailDialogs.kt
Show resolved
Hide resolved
| data class RecruiterInfoForReview( | ||
| val name: String, | ||
| val profileUrl: String, | ||
| val rating: String, | ||
| ) |
There was a problem hiding this comment.
profileUrl을 nullable 타입으로 변경하시는 것을 권장드려요.
HistoryDeliveryReviewModal의 recruiterProfileUrl 파라미터가 String? 타입인데, RecruiterInfoForReview.profileUrl은 String 타입이에요. 프로필 URL이 없는 경우를 안전하게 처리하기 위해 nullable로 변경하시는 것이 좋습니다.
💡 제안된 수정 사항
data class RecruiterInfoForReview(
val name: String,
- val profileUrl: String,
+ val profileUrl: String?,
val rating: String,
)🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/component/ParticipantDetailDialogs.kt`
around lines 74 - 78, RecruiterInfoForReview의 profileUrl을 nullable로 변경하세요: data
class RecruiterInfoForReview(val name: String, val profileUrl: String?)로 타입을
수정하고 RecruiterInfoForReview를 생성하는 모든 지점과 이를 사용하는 코드(예:
HistoryDeliveryReviewModal의 recruiterProfileUrl 전달부)를 찾아 nullable을 처리하도록
업데이트하세요; null일 때의 UI/로직(프로필 이미지 로딩, 인텐트/링크 열기 등)은 안전 호출이나 기본값(또는 분기 처리)으로 대비하도록
변경하세요.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/src/main/java/com/poti/android/domain/model/history/HistoryListType.kt (1)
3-17: 파일명 수정 필요 -HistoryListContent.kt로 변경하세요.코드 자체는 잘 작성되었습니다. Domain 레이어가 순수 Kotlin으로 유지되고 있고, 모든 프로퍼티가
val로 불변성을 보장하고 있네요.HistoryListType단일 enum으로 통합한 점도 좋습니다.다만 파일명을 수정해야 합니다. 현재
HistoryListType.kt라고 되어 있는데, 이 파일의 주요 클래스는HistoryListContent와HistoryItem입니다. 프로젝트 전체 명명 규칙에 따르면 파일명은 주요 클래스명과 일치해야 하므로, 이 파일을HistoryListContent.kt로 변경하시기 바랍니다. 특히domain/type/HistoryListType.kt와의 혼동을 피하기 위해 더욱 필요합니다.app/src/main/java/com/poti/android/presentation/history/component/HistoryCardItem.kt (1)
73-147: Row 전체를 클릭 가능하도록 변경해줘야 합니다.
현재 코드는 아이콘에만 click handler가 있고, Row는 클릭 불가능한 상태예요. 때문에isPressed상태도 아이콘을 터치할 때만 반응해서 눌림 효과(배경색 변경)가 아이콘 영역에서만 보여요. 전체 카드가 상호작용 가능해 보이는데 실제로는 터치 타깃이 줄어들어 있으니, Row에noRippleClickable()을 옮기고 Icon에서 제거하는 게 좋습니다.🔧 제안 수정
Row( modifier = modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) .background(if (isPressed) colors.gray100 else colors.white) + .noRippleClickable( + interactionSource = interactionSource, + onClick = onClick, + ) .padding(8.dp) .height(IntrinsicSize.Min), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp), ) { ... Icon( painter = painterResource(id = R.drawable.ic_arrow_right_lg), contentDescription = null, tint = colors.gray700, - modifier = Modifier - .fillMaxHeight() - .noRippleClickable( - interactionSource = interactionSource, - onClick = onClick, - ), + modifier = Modifier.fillMaxHeight(), ) }
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/poti/android/presentation/history/recruiter/DummyRecruiterData.kt`:
- Line 100: DummyRecruiterData의 participantCount 값이 participantInfoList의 실제 항목
수와 일치하지 않습니다—participantInfoList에는 6개의 ParticipantInfo가 있으므로 participantCount를
하드코딩된 1로 두지 말고 participantInfoList.size 또는 실제 항목수(예: 6)로 변경하세요; 대상 심볼:
DummyRecruiterData, participantInfoList, participantCount.
In
`@app/src/main/java/com/poti/android/presentation/history/recruiter/RecruiterViewModel.kt`:
- Around line 39-44: Replace the temporary dummy flow in getRecruiterDetail with
the repository call and handle success/failure: call
userRepository.getRecruiterDetail(recruitId) and chain .onSuccess to call
updateState { copy(recruiterDetailState = ApiState.Success(data.toUiModel())) }
and .onFailure to call updateState { copy(recruiterDetailState =
ApiState.Failure(error.message ?: "Failed")) }; ensure you keep the coroutine
context (launchScope) and reference recruiterDetailState, ApiState, updateState,
and getRecruiterDetail when making the change.
♻️ Duplicate comments (3)
app/src/main/java/com/poti/android/presentation/history/recruiter/RecruiterViewModel.kt (1)
23-29: 유효하지 않은recruitId처리에 대한 이전 리뷰 의견이 여전히 적용됩니다.
recruitId가-1L인 경우ApiState.Loading상태로 설정하는 것은 사용자에게 적절한 피드백을 제공하지 못해요. 이전 리뷰에서 제안된 대로ApiState.Failure로 처리하거나NavigateBack효과를 발생시키는 것이 더 적절합니다.app/src/main/java/com/poti/android/presentation/history/navigation/HistoryNavigation.kt (1)
71-73: ParticipantManage 목적지에서 recruitId를 추출하여 전달해야 합니다.
HistoryRoute.ParticipantManage는recruitId: Long파라미터를 가지고 있지만, composable 블록에서 route 파라미터를 추출하지 않아ParticipantManageRoute에 전달되지 않습니다. Navigation Compose의 타입 안전 API를 사용하여 route에서 파라미터를 추출해 주세요.🔧 제안 수정
- composable<HistoryRoute.ParticipantManage> { - ParticipantManageRoute(modifier = Modifier.padding(paddingValues)) + composable<HistoryRoute.ParticipantManage> { backStackEntry -> + val route = backStackEntry.toRoute<HistoryRoute.ParticipantManage>() + ParticipantManageRoute( + recruitId = route.recruitId, + modifier = Modifier.padding(paddingValues), + ) }
toRoute()확장 함수를 사용하려면 import가 필요합니다:import androidx.navigation.toRouteapp/src/main/java/com/poti/android/presentation/history/recruiter/RecruiterDetailScreen.kt (1)
58-66: Success 외 상태에서 화면이 비어 보입니다.
recruiterDetailState가 Loading/Failure일 때 아무 UI도 노출되지 않습니다. 로딩/에러 표시를 추가해 주세요.💡 제안 수정
- uiState.recruiterDetailState.onSuccess { recruiterDetail -> - RecruiterDetailScreen( - recruiterDetail = recruiterDetail, - onBackClick = { viewModel.processIntent(RecruiterDetailUiIntent.BackButtonClicked) }, - onDetailClick = { viewModel.processIntent(RecruiterDetailUiIntent.PartyCardClicked) }, - onParticipantManageDetailClick = { viewModel.processIntent(RecruiterDetailUiIntent.ParticipantSectionClicked) }, - modifier = modifier, - ) - } + when (val state = uiState.recruiterDetailState) { + is ApiState.Success -> { + RecruiterDetailScreen( + recruiterDetail = state.data, + onBackClick = { viewModel.processIntent(RecruiterDetailUiIntent.BackButtonClicked) }, + onDetailClick = { viewModel.processIntent(RecruiterDetailUiIntent.PartyCardClicked) }, + onParticipantManageDetailClick = { viewModel.processIntent(RecruiterDetailUiIntent.ParticipantSectionClicked) }, + modifier = modifier, + ) + } + is ApiState.Loading -> { + // 로딩 UI 표시 + } + is ApiState.Failure -> { + PotiEmptyStateInline(text = state.message) + } + }
🧹 Nitpick comments (6)
app/src/main/java/com/poti/android/presentation/history/list/model/Contracts.kt (1)
38-45: 계산 프로퍼티 사용에 대한 참고 사항입니다.현재
items,ongoingCount,endedCount가 매 접근 시마다 캐스팅과 널 체크를 수행합니다. 현재 구조에서는 문제없지만, Composable에서 자주 접근하게 된다면derivedStateOf나 별도 캐싱을 고려해 보실 수 있습니다.app/src/main/java/com/poti/android/presentation/history/list/HistoryListViewModel.kt (1)
79-79:createDummyContent함수의 가시성을private으로 변경하는 것을 권장드려요.ViewModel 내부에서만 사용되는 더미 데이터 생성 함수이므로,
private으로 선언하여 캡슐화를 유지하는 것이 좋습니다.♻️ 수정 제안
- fun createDummyContent(): HistoryListContent { + private fun createDummyContent(): HistoryListContent {app/src/main/java/com/poti/android/presentation/history/recruiter/model/RecruiterDetailUiModel.kt (1)
38-40: 배송 정보 문자열 조합 방식에 대해 검토해 주세요.
shippingInfo를"$receiverName\n$address\n$phone"형태로 조합하고 있는데, UI에서 각 필드를 별도로 스타일링하거나 레이아웃을 변경해야 할 경우 유연성이 떨어질 수 있어요. 현재 UI 요구사항에서 이 방식이 충분하다면 괜찮지만, 향후 변경 가능성을 고려해 주세요.app/src/main/java/com/poti/android/presentation/history/participant/model/ParticipantDetailUiModel.kt (1)
11-26: Compose 안정성을 위해@Immutable어노테이션 적용을 고려해 주세요.
ParticipantDetailUiModel은 여러 필드를 포함하고 있으며, Compose State로 사용될 때 불필요한 리컴포지션을 방지하기 위해@Immutable어노테이션을 추가하는 것이 좋습니다. 특히List타입을 포함하는DepositInfoUiModel에도 적용하면 Compose 컴파일러가 안정성을 보장할 수 있습니다.♻️ 제안 수정
+import androidx.compose.runtime.Immutable + +@Immutable data class ParticipantDetailUiModel( val recruitId: Long, // ... )
DepositInfoUiModel에도 동일하게 적용:+@Immutable data class DepositInfoUiModel( val items: List<DepositItemUiModel>, // ... )app/src/main/java/com/poti/android/presentation/history/component/PartyInfoSection.kt (1)
18-43: Domain 모델을 UI 레이어에서 직접 사용하고 있습니다.
PartySummary는 Domain 레이어의 모델로 보입니다. Clean Architecture 원칙에 따르면 UI 레이어는 자체 UI 모델을 사용하고, Domain 모델은 Mapper를 통해 변환하는 것이 권장됩니다. 현재 구현도 동작에는 문제가 없지만, 향후 유지보수성을 위해PartySummaryUiModel을 만들어 사용하는 것을 고려해 주세요.// UI 모델 예시 data class PartySummaryUiModel( val imageUrl: String, val artist: String, val title: String, `@StringRes` val statusTextId: Int, val statusColor: Color, )app/src/main/java/com/poti/android/presentation/history/component/ProgressStatusSection.kt (1)
70-112: Step 3 (SHIPPING) 상태의 Preview가 누락되어 있습니다.현재 Step 0, 1, 2, 4에 대한 Preview만 있고,
PartyStatusType.SHIPPING상태의 Preview가 없습니다. 완전한 UI 검증을 위해 Step 3 Preview 추가를 고려해 주세요.♻️ 제안 추가
`@Preview`(showBackground = true, name = "Step 3 (Shipping)") `@Composable` private fun ProgressStatusSectionStep3Preview() { PotiTheme { ProgressStatusSection( progressStatus = PartyStatusType.SHIPPING, statusMessage = "배송 중이에요", ) } }
| ), | ||
| ), | ||
| ), | ||
| participantCount = 1, |
There was a problem hiding this comment.
participantCount 값이 실제 리스트 크기와 일치하지 않아요.
participantInfoList에는 6개의 ParticipantInfo가 있지만, participantCount는 1로 설정되어 있습니다. 더미 데이터이지만 UI 테스트 시 혼란을 줄 수 있으니 수정해 주세요.
🐛 수정 제안
),
- participantCount = 1,
+ participantCount = 6,
)🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/recruiter/DummyRecruiterData.kt`
at line 100, DummyRecruiterData의 participantCount 값이 participantInfoList의 실제 항목
수와 일치하지 않습니다—participantInfoList에는 6개의 ParticipantInfo가 있으므로 participantCount를
하드코딩된 1로 두지 말고 participantInfoList.size 또는 실제 항목수(예: 6)로 변경하세요; 대상 심볼:
DummyRecruiterData, participantInfoList, participantCount.
| private fun getRecruiterDetail(recruitId: Long) = launchScope { | ||
| // TODO: [천민재] 서버 연결 필요 | ||
| updateState { | ||
| copy(recruiterDetailState = ApiState.Success(dummyRecruiterData.toUiModel())) | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "BaseViewModel.kt" -o -name "RecruiterViewModel.kt" | head -20Repository: team-poti/POTI-ANDROID
Length of output: 219
🏁 Script executed:
fd -t f "BaseViewModel\.kt"Repository: team-poti/POTI-ANDROID
Length of output: 127
🏁 Script executed:
fd -t f "RecruiterViewModel\.kt"Repository: team-poti/POTI-ANDROID
Length of output: 153
🏁 Script executed:
wc -l ./app/src/main/java/com/poti/android/core/base/BaseViewModel.ktRepository: team-poti/POTI-ANDROID
Length of output: 132
🏁 Script executed:
cat -n ./app/src/main/java/com/poti/android/core/base/BaseViewModel.ktRepository: team-poti/POTI-ANDROID
Length of output: 1950
🏁 Script executed:
wc -l ./app/src/main/java/com/poti/android/presentation/history/recruiter/RecruiterViewModel.ktRepository: team-poti/POTI-ANDROID
Length of output: 158
🏁 Script executed:
cat -n ./app/src/main/java/com/poti/android/presentation/history/recruiter/RecruiterViewModel.ktRepository: team-poti/POTI-ANDROID
Length of output: 2360
🏁 Script executed:
# Search for onError usage patterns in ViewModels
rg "onError\s*=" app/src/main/java --type kt -B 2 -A 2Repository: team-poti/POTI-ANDROID
Length of output: 92
🏁 Script executed:
# Find RecruiterDetailUiState to check for error state
fd -t f "RecruiterDetailUiState" -o -name "*RecruiterDetailUiState*"Repository: team-poti/POTI-ANDROID
Length of output: 296
🏁 Script executed:
# Search for onError usage patterns in ViewModels
rg "onError\s*=" app/src/main/java -g "*.kt" -B 2 -A 2Repository: team-poti/POTI-ANDROID
Length of output: 48
🏁 Script executed:
# Find RecruiterDetailUiState file
fd "RecruiterDetailUiState" app/src/main/javaRepository: team-poti/POTI-ANDROID
Length of output: 48
🏁 Script executed:
# Search for RecruiterDetailUiState definition
rg "RecruiterDetailUiState" app/src/main/java -g "*.kt" -B 2 -A 5Repository: team-poti/POTI-ANDROID
Length of output: 3348
🏁 Script executed:
# Search for any UI state definitions with error handling
rg "class.*UiState" app/src/main/java/com/poti/android/presentation -g "*.kt" -A 10 | head -100Repository: team-poti/POTI-ANDROID
Length of output: 10831
🏁 Script executed:
# Find ApiState definition
fd "ApiState" app/src/main/java -g "*.kt" | head -5Repository: team-poti/POTI-ANDROID
Length of output: 116
🏁 Script executed:
# Search for launchScope with onError usage
rg "launchScope\(" app/src/main/java -g "*.kt" -A 3 | head -50Repository: team-poti/POTI-ANDROID
Length of output: 430
🏁 Script executed:
# Find ApiState definition
rg "sealed.*ApiState\|class ApiState\|interface ApiState" app/src/main/java -g "*.kt" -A 8Repository: team-poti/POTI-ANDROID
Length of output: 48
🏁 Script executed:
# Check how other ViewModels handle errors with launchScope
rg "launchScope" app/src/main/java/com/poti/android/presentation -g "*.kt" -B 2 -A 5Repository: team-poti/POTI-ANDROID
Length of output: 8365
🏁 Script executed:
# Search for ApiState in common directory
rg "class ApiState\|sealed class ApiState" app/src/main/java/com/poti/android/core/common -g "*.kt" -A 10Repository: team-poti/POTI-ANDROID
Length of output: 48
🏁 Script executed:
# Look at how onFailure is used - examine one complete example
cat -n app/src/main/java/com/poti/android/presentation/onboarding/OnboardingViewModel.kt | head -80Repository: team-poti/POTI-ANDROID
Length of output: 4064
서버 연결 시 에러 상태 처리 추가 필요
현재 코드는 더미 데이터를 사용하고 있어서 문제없지만, 서버 연결 후에는 다른 ViewModels의 패턴을 따라 repository 호출 결과를 .onSuccess와 .onFailure로 처리하고, 에러 시 ApiState.Failure로 상태를 업데이트해 주세요.
예:
userRepository.getRecruiterDetail(recruitId)
.onSuccess { data ->
updateState { copy(recruiterDetailState = ApiState.Success(data.toUiModel())) }
}
.onFailure { error ->
updateState { copy(recruiterDetailState = ApiState.Failure(error.message ?: "Failed")) }
}
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/recruiter/RecruiterViewModel.kt`
around lines 39 - 44, Replace the temporary dummy flow in getRecruiterDetail
with the repository call and handle success/failure: call
userRepository.getRecruiterDetail(recruitId) and chain .onSuccess to call
updateState { copy(recruiterDetailState = ApiState.Success(data.toUiModel())) }
and .onFailure to call updateState { copy(recruiterDetailState =
ApiState.Failure(error.message ?: "Failed")) }; ensure you keep the coroutine
context (launchScope) and reference recruiterDetailState, ApiState, updateState,
and getRecruiterDetail when making the change.
Related issue 🛠️
Work Description ✏️
Screenshot 📸
Uncompleted Tasks 😅
To Reviewers 📢
Summary by CodeRabbit
새로운 기능
✏️ Tip: You can customize this high-level summary in your review settings.