Skip to content

[Feat/#88] 참여자 상세 뷰 UI 구현#105

Merged
jyvnee merged 12 commits intodevelopfrom
feat/#88-participant-detail-ui
Jan 22, 2026
Merged

[Feat/#88] 참여자 상세 뷰 UI 구현#105
jyvnee merged 12 commits intodevelopfrom
feat/#88-participant-detail-ui

Conversation

@jyvnee
Copy link
Copy Markdown
Contributor

@jyvnee jyvnee commented Jan 21, 2026

Related issue 🛠️

Work Description ✏️

  • 참여자 상세 뷰 UI 구현

Screenshot 📸

스크린샷 1 스크린샷 2

Uncompleted Tasks 😅

  • 바텀시트 연결
  • 모달 연결

To Reviewers 📢

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 참가자 상세에 주문번호·파티 식별 정보 반영 및 결제·배송 정보 표시 방식 개선
    • 입금·배송 상태 섹션 강화 — 배송방법·추적정보 노출, 회원별 결제 내역·수수료·총액 표시
    • 모달/바텀시트 기반의 입금·배송 확인·리뷰 오버레이 추가
    • 상세 화면 헤더·내비게이션 동작(뒤로가기·파티 이동) 개선
  • 버그 수정

    • 입금 입력 버튼 활성화 조건 강화 — 필수 항목 검증 보완

✏️ Tip: You can customize this high-level summary in your review settings.

@jyvnee jyvnee self-assigned this Jan 21, 2026
@jyvnee jyvnee added 🌟 Feat 새로운 기능 구현 🔎 UI UI 작업 ✨ 지현 labels Jan 21, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 21, 2026

📝 Walkthrough

Walkthrough

참여자 상세 화면을 MVVM으로 재구현하고 도메인 모델에 partyId/orderNumber를 추가했으며, 배송·결제 타입을 재구성하고 여러 UI 컴포넌트를 삭제·이동해 오버레이 기반 상태 흐름을 도입했습니다. (50단어 이내)

Changes

Cohort / File(s) 요약
도메인 모델 업데이트
app/src/main/java/com/poti/android/domain/model/history/ParticipantDetail.kt
partyId: Long, orderNumber: String 추가; ShippingInfoParticipantShippingInfo 변경; PaymentInfobank, accountNumber, depositDeadline을 nullable로 변경; 새 ParticipantShippingInfo 타입 추가 — 도메인 계약 변경 사항 확인 필요
UI 모델 & 매핑
app/src/main/java/com/poti/android/presentation/history/participant/model/ParticipantDetailUiModel.kt, .../ParticipantButtonState.kt
UI 모델 단순화(도메인 의존), PaymentInfoUiModel, ParticipantShippingUiModel 도입, ParticipantDetail.toUiModel() 추가, 버튼 상태 로직 변경 — 매핑 로직 및 버튼 상태 조건 검증 필요
MVVM 도입 / 라우팅 변경
app/src/main/java/com/poti/android/presentation/history/participant/ParticipantViewModel.kt, .../ParticipantDetailScreen.kt, app/src/main/java/com/poti/android/presentation/history/navigation/HistoryNavigation.kt
ViewModel을 BaseViewModel+SavedStateHandle로 전환, intent/effect/overlay 상태 추가, Route 시그니처에 onPopBackStack/onNavigateToPartyDetail 콜백 전달 — 내비게이션 콜백 시그니처 일치 확인 필요
오버레이 상태 및 계약 변경
app/src/main/java/com/poti/android/presentation/history/participant/model/Contract.kt, .../ParticipantDetailOverlayState.kt
오버레이 상태(DepositBottomSheet, DeliveryConfirmModal, DeliveryReviewModal) 추가 및 관련 인텐트/이펙트 추가 — 상태 전환 경로 점검 필요
신규/이전된 컴포넌트 (participant 패키지)
app/src/main/java/com/poti/android/presentation/history/participant/component/DeliveryStatusContent.kt, DepositStatusContent.kt, HistoryCalloutInfo.kt, HistoryDeliveryModal.kt, HistoryDepositBottomSheet.kt
배송/입금 상태 렌더링용 새 Composable 추가 및 일부 파일 패키지 이동, 버튼 enabled 조건 추가 — UI 동작 및 복사 가능 텍스트/트래킹 처리 점검
삭제된 UI 컴포넌트
app/src/main/java/com/poti/android/presentation/history/component/DepositInfoSection.kt, HistoryStateLabel.kt, PriceDetail.kt, CommonUiModel.kt
여러 기존 UI 컴포넌트 및 모델 삭제 — 호출 지점/참조 제거 여부 확인 필요
헤더·리크루터 화면 조정
app/src/main/java/com/poti/android/presentation/history/component/HistoryDetailContentHeader.kt, .../recruiter/RecruiterDetailScreen.kt
헤더 시그니처 변경(문구 파라미터화·옵셔널 아이콘), Recruiter 화면에서 레이아웃 조정 — 기존 호출부 호환성 점검
리소스·기타
app/src/main/res/values/strings.xml, .gitignore
history_delivery_method 문자열 추가; .gitignoreGEMINI.md 추가

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant Route as ParticipantDetailRoute
    participant ViewModel as ParticipantViewModel
    participant Repo as Repository
    participant Screen as ParticipantDetailScreen
    participant UI as UI_컴포넌트

    rect rgba(200,200,255,0.5)
    User->>Route: 참여 상세 화면 진입
    Route->>ViewModel: ViewModel 인스턴스 생성/주입
    ViewModel->>Repo: getParticipantDetail(participationId)
    Repo-->>ViewModel: ParticipantDetail (domain)
    ViewModel->>ViewModel: toUiModel() 변환
    ViewModel-->>Screen: uiState(성공) 발행
    end

    rect rgba(200,255,200,0.5)
    User->>Screen: 입금 버튼 클릭
    Screen->>ViewModel: OnDepositCompleteClick 인텐트 발행
    ViewModel->>Screen: overlayState = DepositBottomSheet (uiState 업데이트)
    Screen->>UI: HistoryDepositBottomSheet 렌더링
    User->>UI: 입금 정보 제출
    UI->>Screen: onSubmitDeposit(depositor, time)
    Screen->>ViewModel: SubmitDeposit 인텐트 발행
    ViewModel->>ViewModel: overlayState = None, 상태 갱신
    ViewModel-->>Screen: uiState 업데이트
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • cmj7271
  • doyeon0307
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 변경 사항의 주요 내용인 참여자 상세 뷰 UI 구현을 명확하게 설명하고 있습니다.
Description check ✅ Passed PR 설명이 템플릿 구조를 따르고 관련 이슈, 작업 내용, 스크린샷, 미완료 작업을 포함하고 있습니다.
Linked Issues check ✅ Passed 참여자 상세 뷰 UI 구현 요구사항이 코드 변경사항에서 충실하게 반영되어 있습니다. [#88]
Out of Scope Changes check ✅ Passed 모든 코드 변경사항이 참여자 상세 뷰 UI 구현이라는 기본 목표 범위 내에 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

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: 8

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/presentation/history/participant/component/HistoryDeliveryModal.kt (1)

63-64: 버튼 비활성화 상태를 명확하게 관리해야 합니다.

현재 currentRating == 0일 때 버튼 클릭이 무시되지만, 버튼이 활성화된 것처럼 보여서 사용자 경험이 좋지 않습니다. PotiModalButtonenabled 파라미터를 지원하지만, PotiLargeModal이 이를 노출하고 있지 않아서 직접 전달할 수 없는 상태입니다.

다음 중 하나의 방식으로 개선하시기 바랍니다:

  1. PotiLargeModalbtnEnabled: Boolean = true 파라미터를 추가하여 PotiModalButton에 전달하거나
  2. 현재 조건부 실행 방식에서 사용자에게 시각적 피드백(예: Toast)을 제공하거나
  3. 별도의 상태 관리 로직으로 버튼 활성화 상태를 더 명확하게 표현해주세요.
app/src/main/java/com/poti/android/presentation/history/participant/component/HistoryDepositBottomSheet.kt (1)

40-49: modifier 파라미터 중복 사용 문제가 있습니다.

HistoryDepositBottomSheetmodifier 파라미터가 PotiBottomSheet에도 전달되고(Line 35), BottomSheetContent에도 전달되고 있습니다(Line 46-47). 동일한 modifier가 두 군데에 적용되면 의도치 않은 레이아웃 동작이 발생할 수 있어요.

🐛 수정 제안
         content = {
             BottomSheetContent(
                 depositor = depositor,
                 onDepositorChanged = { depositor = it },
                 depositTime = depositTime,
                 onDepositTimeChanged = { depositTime = it },
-                modifier = modifier
-                    .padding(horizontal = 16.dp),
+                modifier = Modifier
+                    .padding(horizontal = 16.dp),
             )
         },
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/poti/android/presentation/history/participant/component/DeliveryStatusContent.kt`:
- Line 3: The import for ParticipantShippingUiModel is missing its package path
causing a compile error; update the import in DeliveryStatusContent.kt to use
the full package-qualified name where ParticipantShippingUiModel is declared
(replace the bare "import ParticipantShippingUiModel" with the full package
import for ParticipantShippingUiModel), then recompile to ensure the symbol in
DeliveryStatusContent (the composable/component referencing
ParticipantShippingUiModel) resolves correctly.

In
`@app/src/main/java/com/poti/android/presentation/history/participant/component/DepositStatusContent.kt`:
- Around line 3-4: The file has unresolved imports for
ParticipantShippingUiModel and PaymentInfoUiModel; locate the Kotlin/Java
declarations of ParticipantShippingUiModel and PaymentInfoUiModel in the project
and replace the two incorrect import lines in DepositStatusContent.kt with their
correct fully-qualified class names (use the exact package + class names as
declared); save and recompile to confirm the compile error is resolved.

In
`@app/src/main/java/com/poti/android/presentation/history/participant/model/Contract.kt`:
- Line 3: The import line "import ParticipantDetailUiModel" is invalid for a
type in the same package; remove that import or replace it with the
fully-qualified class name where used (referencing ParticipantDetailUiModel in
Contract.kt) so the compiler can resolve the symbol—either delete the stray
import statement or reference
com.poti.android.presentation.history.participant.model.ParticipantDetailUiModel
(FQCN) where needed.

In
`@app/src/main/java/com/poti/android/presentation/history/participant/model/ParticipantDetailUiModel.kt`:
- Around line 1-7: This file is missing its package declaration: add "package
com.poti.android.presentation.history.participant.model" at the very top of the
file so the declarations (imports and types like ParticipantDetailUiModel /
ParticipantButtonState references) compile in the correct package; ensure the
package line appears before any imports or other code so other modules can
reference classes from this file.

In
`@app/src/main/java/com/poti/android/presentation/history/participant/ParticipantDetailScreen.kt`:
- Around line 40-48: The UI currently only handles
uiState.participantDetailState.onSuccess and leaves Loading/Error blank; update
the composable to handle all states (Loading -> show a progress indicator, Error
-> show an error message/retry) by switching on uiState.participantDetailState
(e.g., when/collectAsState or onLoading/onError handlers) and render
ParticipantDetailScreen only on success; also wire the action lambdas so
onDetailClick and onActionButtonClick call the ViewModel's processIntent (e.g.,
dispatch appropriate intents from the ViewModel) instead of empty lambdas,
keeping onBackClick as onPopBackStack.
- Line 3: 현재 ParticipantDetailScreen.kt의 import문이 패키지 경로 없이 "import
ParticipantDetailUiModel"로 되어 있어 컴파일 오류가 납니다; ParticipantDetailUiModel이 선언된 실제
패키지(예: com.poti.android.presentation.history.participant.model 또는 프로젝트 내 해당
패키지)를 찾아 해당 전체 경로로 import문을 교체하고 기존 잘못된 import 라인을 제거하세요 (참조 대상:
ParticipantDetailUiModel, 파일: ParticipantDetailScreen.kt).

In
`@app/src/main/java/com/poti/android/presentation/history/participant/ParticipantViewModel.kt`:
- Around line 3-5: Replace the bare imports at the top of
ParticipantViewModel.kt by adding full package-qualified import statements for
ParticipantDetailUiModel, ParticipantShippingUiModel, and PaymentInfoUiModel;
locate the real packages where those classes are declared (e.g., their UI/model
packages) and update the import lines for the symbols ParticipantDetailUiModel,
ParticipantShippingUiModel, and PaymentInfoUiModel so the compiler can resolve
them.
- Around line 34-46: Replace the runtime-crashing TODO() placeholders in
ParticipantViewModel.processIntent with no-op or safe stub implementations: for
each branch handling ParticipantDetailUiIntent (e.g., LoadDetail, OnBackClick,
OnPartyDetailClick, OnDepositCompleteClick, SubmitDeposit, OnDeliveredClick,
ConfirmDelivery, SkipReview, SubmitReview) return Unit and/or log the intent
instead of calling TODO(); ensure processIntent compiles and does not throw by
removing all TODO() invocations and using simple stubs (logging or empty
handlers) until real logic is implemented.
🧹 Nitpick comments (5)
app/src/main/java/com/poti/android/presentation/history/component/HistoryCardItem.kt (1)

60-91: Nullable imageUrl 처리에 대한 기본 이미지/placeholder 추가 권장

imageUrlnull일 때 AsyncImage가 빈 영역으로 남을 수 있어 UX가 떨어질 수 있습니다. Coil 버전에 따라 동작이 달라질 수 있으니, placeholder/error 또는 기본 리소스를 넣는 방식으로 안전하게 처리하는 편이 좋겠습니다.

♻️ 제안 변경 예시
 AsyncImage(
-    model = imageUrl,
+    model = imageUrl,
+    placeholder = painterResource(R.drawable.img_placeholder),
+    error = painterResource(R.drawable.img_placeholder),
     contentDescription = null,
     modifier = Modifier
         .fillMaxHeight()
         .aspectRatio(1f)
         .clip(RoundedCornerShape(8.dp)),
     contentScale = ContentScale.Crop,
 )
app/src/main/java/com/poti/android/presentation/history/participant/component/HistoryDeliveryModal.kt (1)

71-77: 하드코딩된 dp 값을 Dimens 리소스로 관리하는 것을 고려해 보세요.

현재 16.dp, 28.dp, 10.dp, 12.dp 등 여러 값이 하드코딩되어 있습니다. 디자인 시스템의 일관성과 유지보수성을 위해 프로젝트에서 사용하는 Dimens 객체나 리소스로 관리하시면 추후 디자인 변경 시 한 곳에서 수정할 수 있어 편리합니다.

다만 현재 구현도 기능적으로 문제없으므로, 팀 컨벤션에 따라 결정해 주시면 됩니다.

app/src/main/java/com/poti/android/presentation/history/participant/component/HistoryDepositBottomSheet.kt (1)

72-78: 하드코딩된 패딩 값에 대한 설명이 필요합니다.

226.dp는 매직 넘버로 보입니다. 키보드 회피를 위한 값이라면 WindowInsets를 활용하거나, 의도를 명확히 하는 상수나 주석을 추가하시는 것이 유지보수에 도움이 될 것 같습니다.

app/src/main/java/com/poti/android/presentation/history/participant/component/DeliveryStatusContent.kt (1)

25-75: 전반적인 구현은 잘 되어 있습니다.

  • Modifier가 첫 번째 선택적 파라미터로 올바르게 위치해 있습니다.
  • screenWidthDp를 사용한 반응형 패딩 처리가 좋습니다.
  • PotiTheme의 색상과 타이포그래피를 일관되게 사용하고 있네요.
  • when 표현식으로 상태별 UI 분기가 명확합니다.

다만, whenelse 브랜치가 빈 블록으로 처리되어 있는데, 향후 새로운 ParticipantStatusType이 추가될 경우 누락될 수 있습니다. 가능하다면 exhaustive when을 사용하거나, 의도적으로 무시하는 상태들을 명시적으로 나열하시는 것을 권장드립니다.

app/src/main/java/com/poti/android/presentation/history/participant/ParticipantViewModel.kt (1)

48-88: 하드코딩된 Mock 데이터 및 Domain 모델 직접 사용에 대한 검토가 필요합니다.

현재 구현에서 몇 가지 개선 포인트가 있어요:

  1. UseCase/Repository 누락: 실제 데이터를 가져오는 UseCaseRepository가 주입되지 않았습니다. UI 확인용 Mock 데이터라면 이해되지만, 추후 연결이 필요해요.

  2. Domain 모델 직접 사용: PartySummary, MemberPayment 같은 Domain 모델이 UI Layer에서 직접 사용되고 있습니다. Clean Architecture 원칙에 따라 toUiModel() 같은 Mapper를 통해 변환하시는 것을 권장드려요.

  3. 에러 처리 누락: launchScope 사용 시 onError 콜백이 제공되지 않아 예외 발생 시 무시됩니다.

🔧 개선 방향 제안
`@HiltViewModel`
class ParticipantViewModel `@Inject` constructor(
    savedStateHandle: SavedStateHandle,
    private val getParticipantDetailUseCase: GetParticipantDetailUseCase, // UseCase 주입
) : BaseViewModel<...>(...) {

    private fun getParticipantDetail(participantId: Long) = launchScope(
        onError = { e -> 
            updateState { copy(participantDetailState = ApiState.Error(e)) }
        }
    ) {
        val result = getParticipantDetailUseCase(participantId)
        updateState {
            copy(participantDetailState = ApiState.Success(result.toUiModel()))
        }
    }
}

Comment on lines +40 to +48
uiState.participantDetailState.onSuccess { participantDetail ->
ParticipantDetailScreen(
participantDetail = participantDetail,
onBackClick = onPopBackStack,
onDetailClick = {},
onActionButtonClick = {},
modifier = modifier,
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Loading 및 Error 상태 처리가 누락되었습니다.

현재 onSuccess만 처리하고 있어 Loading/Error 상태에서는 화면이 빈 상태로 표시됩니다. UX 개선을 위해 각 상태에 대한 처리를 추가하시는 것을 권장드려요.

또한 onDetailClickonActionButtonClick이 빈 람다로 전달되어 ViewModel의 Intent와 연결되지 않았습니다. MVI 패턴의 단방향 데이터 흐름을 완성하려면 ViewModel의 processIntent를 호출해야 해요.

🔧 상태 처리 및 Intent 연결 제안
 `@Composable`
 fun ParticipantDetailRoute(
     onPopBackStack: () -> Unit,
     modifier: Modifier = Modifier,
     viewModel: ParticipantViewModel = hiltViewModel(),
 ) {
     val uiState by viewModel.uiState.collectAsStateWithLifecycle()

-    uiState.participantDetailState.onSuccess { participantDetail ->
+    when (val state = uiState.participantDetailState) {
+        is ApiState.Loading -> {
+            // Loading UI (예: CircularProgressIndicator)
+        }
+        is ApiState.Error -> {
+            // Error UI (예: 에러 메시지 및 재시도 버튼)
+        }
+        is ApiState.Success -> {
+            ParticipantDetailScreen(
+                participantDetail = state.data,
+                onBackClick = { viewModel.processIntent(ParticipantDetailUiIntent.OnBackClick) },
+                onDetailClick = { viewModel.processIntent(ParticipantDetailUiIntent.OnPartyDetailClick) },
+                onActionButtonClick = { buttonState ->
+                    when (buttonState) {
+                        ParticipantButtonState.DEPOSIT_DONE -> 
+                            viewModel.processIntent(ParticipantDetailUiIntent.OnDepositCompleteClick)
+                        ParticipantButtonState.DELIVERY_RECEIVED -> 
+                            viewModel.processIntent(ParticipantDetailUiIntent.OnDeliveredClick)
+                        ParticipantButtonState.NONE -> {}
+                    }
+                },
+                modifier = modifier,
+            )
+        }
+        else -> {}
+    }
-        ParticipantDetailScreen(
-            participantDetail = participantDetail,
-            onBackClick = onPopBackStack,
-            onDetailClick = {},
-            onActionButtonClick = {},
-            modifier = modifier,
-        )
-    }
 }
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/participant/ParticipantDetailScreen.kt`
around lines 40 - 48, The UI currently only handles
uiState.participantDetailState.onSuccess and leaves Loading/Error blank; update
the composable to handle all states (Loading -> show a progress indicator, Error
-> show an error message/retry) by switching on uiState.participantDetailState
(e.g., when/collectAsState or onLoading/onError handlers) and render
ParticipantDetailScreen only on success; also wire the action lambdas so
onDetailClick and onActionButtonClick call the ViewModel's processIntent (e.g.,
dispatch appropriate intents from the ViewModel) instead of empty lambdas,
keeping onBackClick as onPopBackStack.

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

🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/poti/android/presentation/history/participant/ParticipantDetailScreen.kt`:
- Around line 60-63: The onDetailClick is passing the wrong identifier: change
the argument to ParticipantDetailUiIntent.OnPartyDetailClick from
participantDetail.participationId to participantDetail.partyId in the
onDetailClick lambda inside ParticipantDetailScreen (where
viewModel.processIntent is called) so the partyId from the model is used when
navigating to the party details.
♻️ Duplicate comments (5)
app/src/main/java/com/poti/android/presentation/history/participant/model/Contract.kt (1)

3-3: 패키지 경로 없는 import는 컴파일 오류입니다.

동일 패키지 타입은 import 없이 사용하거나 FQCN으로 지정해야 합니다.

🛠️ 수정 제안
-import ParticipantDetailUiModel
app/src/main/java/com/poti/android/presentation/history/participant/model/ParticipantDetailUiModel.kt (1)

1-7: 패키지 선언 누락으로 기본 패키지에 컴파일됩니다.

다른 파일에서 참조가 깨질 수 있어 최상단에 패키지 선언을 추가해 주세요.

🛠️ 수정 제안
+package com.poti.android.presentation.history.participant.model
+
 import com.poti.android.domain.model.history.MemberPayment
 import com.poti.android.domain.model.history.ParticipantDetail
 import com.poti.android.domain.model.history.PartySummary
app/src/main/java/com/poti/android/presentation/history/participant/ParticipantViewModel.kt (1)

3-5: 패키지 경로 없는 import는 컴파일 오류입니다.

해당 타입들은 UI 모델 패키지의 FQCN으로 import 해야 합니다.

🛠️ 수정 제안
-import ParticipantDetailUiModel
-import ParticipantShippingUiModel
-import PaymentInfoUiModel
+import com.poti.android.presentation.history.participant.model.ParticipantDetailUiModel
+import com.poti.android.presentation.history.participant.model.ParticipantShippingUiModel
+import com.poti.android.presentation.history.participant.model.PaymentInfoUiModel
app/src/main/java/com/poti/android/presentation/history/participant/ParticipantDetailScreen.kt (2)

3-3: 패키지 경로 없는 import는 컴파일 오류입니다.

정확한 패키지 경로로 import 해주세요.

🛠️ 수정 제안
-import ParticipantDetailUiModel
+import com.poti.android.presentation.history.participant.model.ParticipantDetailUiModel

55-56: Loading/Error 상태가 표시되지 않아 빈 화면이 됩니다.

Success 외 상태를 분기 처리해 로딩/에러 UI를 제공해주세요.

🛠️ 수정 예시
-    uiState.participantDetailState.onSuccess { participantDetail ->
-        ParticipantDetailScreen(
-            participantDetail = participantDetail,
-            overlayState = uiState.overlayState,
-            onBackClick = onPopBackStack,
-            onDetailClick = {
-                viewModel.processIntent(ParticipantDetailUiIntent.OnPartyDetailClick(participantDetail.partyId))
-            },
-            onActionButtonClick = { buttonState ->
-                if (buttonState == ParticipantButtonState.DEPOSIT_DONE) {
-                    viewModel.processIntent(ParticipantDetailUiIntent.OnDepositCompleteClick)
-                } else {
-                    viewModel.processIntent(ParticipantDetailUiIntent.OnDeliveredClick)
-                }
-            },
-            onOverlayClose = { viewModel.processIntent(ParticipantDetailUiIntent.CloseOverlay) },
-            onSubmitDeposit = { depositor, depositTime ->
-                viewModel.processIntent(ParticipantDetailUiIntent.SubmitDeposit(depositor, depositTime))
-            },
-            onConfirmDelivery = { viewModel.processIntent(ParticipantDetailUiIntent.ConfirmDelivery) },
-            onSubmitReview = { rating -> viewModel.processIntent(ParticipantDetailUiIntent.SubmitReview(rating)) },
-            onSkipReview = { viewModel.processIntent(ParticipantDetailUiIntent.SkipReview) },
-            modifier = modifier,
-        )
-    }
+    when (val state = uiState.participantDetailState) {
+        is ApiState.Loading -> {
+            // TODO: 로딩 UI
+        }
+        is ApiState.Error -> {
+            // TODO: 에러 UI + 재시도
+        }
+        is ApiState.Success -> {
+            ParticipantDetailScreen(
+                participantDetail = state.data,
+                overlayState = uiState.overlayState,
+                onBackClick = onPopBackStack,
+                onDetailClick = {
+                    viewModel.processIntent(ParticipantDetailUiIntent.OnPartyDetailClick(state.data.partyId))
+                },
+                onActionButtonClick = { buttonState ->
+                    if (buttonState == ParticipantButtonState.DEPOSIT_DONE) {
+                        viewModel.processIntent(ParticipantDetailUiIntent.OnDepositCompleteClick)
+                    } else {
+                        viewModel.processIntent(ParticipantDetailUiIntent.OnDeliveredClick)
+                    }
+                },
+                onOverlayClose = { viewModel.processIntent(ParticipantDetailUiIntent.CloseOverlay) },
+                onSubmitDeposit = { depositor, depositTime ->
+                    viewModel.processIntent(ParticipantDetailUiIntent.SubmitDeposit(depositor, depositTime))
+                },
+                onConfirmDelivery = { viewModel.processIntent(ParticipantDetailUiIntent.ConfirmDelivery) },
+                onSubmitReview = { rating -> viewModel.processIntent(ParticipantDetailUiIntent.SubmitReview(rating)) },
+                onSkipReview = { viewModel.processIntent(ParticipantDetailUiIntent.SkipReview) },
+                modifier = modifier,
+            )
+        }
+    }
🧹 Nitpick comments (1)
app/src/main/java/com/poti/android/presentation/history/participant/model/ParticipantDetailUiModel.kt (1)

8-35: UI 모델에 @Immutable 적용을 권장합니다.

UiState에서 List 포함 모델을 직접 보유하므로 안정성 힌트를 주면 불필요한 리컴포지션을 줄일 수 있습니다.

♻️ 적용 예시
+import androidx.compose.runtime.Immutable
 import com.poti.android.domain.model.history.MemberPayment
 import com.poti.android.domain.model.history.ParticipantDetail
 import com.poti.android.domain.model.history.PartySummary
@@
+@Immutable
 data class ParticipantDetailUiModel(
@@
+@Immutable
 data class PaymentInfoUiModel(
@@
+@Immutable
 data class ParticipantShippingUiModel(

코딩 가이드라인 기준입니다.

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

🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/poti/android/presentation/history/participant/model/ParticipantDetailUiModel.kt`:
- Around line 45-53: The accountInfo currently appends shippingInfo.receiver
which can expose the wrong name; update the PaymentInfoUiModel.accountInfo
construction so that when this.paymentInfo.bank and
this.paymentInfo.accountNumber are present you include the actual depositor
field (e.g., this.paymentInfo.depositName or this.paymentInfo.depositor) if
non-null/non-blank, otherwise do NOT append shippingInfo.receiver and just
render "bank accountNumber"; modify the logic in the PaymentInfoUiModel mapping
inside ParticipantDetailUiModel (the paymentInfo -> accountInfo block) to prefer
the depositor field and omit shippingInfo.receiver when depositor is absent.
♻️ Duplicate comments (2)
app/src/main/java/com/poti/android/presentation/history/participant/ParticipantDetailScreen.kt (2)

55-57: Loading/Error 상태 처리가 없어 빈 화면이 될 수 있습니다.
성공 케이스만 처리하고 있어 로딩/에러 시 화면이 비게 됩니다. 상태 분기를 추가해 기본 UI를 노출해 주세요. 코딩 가이드라인에 따라 드리는 의견입니다.

🛠️ 수정 제안
+import com.poti.android.core.common.state.ApiState
@@
-    uiState.participantDetailState.onSuccess { participantDetail ->
-        ParticipantDetailScreen(
-            participantDetail = participantDetail,
+    when (val state = uiState.participantDetailState) {
+        is ApiState.Success -> {
+            val participantDetail = state.data
+            ParticipantDetailScreen(
+                participantDetail = participantDetail,
@@
-        )
-    }
+            )
+        }
+        is ApiState.Loading -> {
+            // TODO: Loading UI
+        }
+        is ApiState.Error -> {
+            // TODO: Error UI / Retry
+        }
+    }

60-63: Party 상세 이동에 잘못된 ID가 전달됩니다.
현재 participationId를 넘기고 있어 잘못된 상세로 이동할 수 있습니다. partyId로 교체해 주세요.

🛠️ 수정 제안
-                viewModel.processIntent(ParticipantDetailUiIntent.OnPartyDetailClick(participantDetail.participationId))
+                viewModel.processIntent(ParticipantDetailUiIntent.OnPartyDetailClick(participantDetail.partyId))
🧹 Nitpick comments (1)
app/src/main/java/com/poti/android/presentation/history/participant/ParticipantViewModel.kt (1)

56-77: TODO로 남은 API 연동은 출시 전 이슈로 추적해 주세요.
현재는 더미/빈 처리라 실제 연동 시점이 명확히 관리되어야 합니다. 필요하시면 구현 방향 정리나 템플릿 작성도 도와드릴게요.

Comment on lines +45 to +53
paymentInfo = PaymentInfoUiModel(
shippingFee = this.paymentInfo.shippingFee,
totalAmount = this.paymentInfo.totalAmount,
depositStatus = this.paymentInfo.depositStatus,
accountInfo = if (this.paymentInfo.bank != null && this.paymentInfo.accountNumber != null) {
"${this.paymentInfo.bank} ${this.paymentInfo.accountNumber} ${this.shippingInfo.receiver}"
} else {
"-"
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

입금 계좌 표기에 수령인 정보가 섞일 수 있습니다.
shippingInfo.receiver가 예금주명이 아니라면 잘못된 계좌 정보로 노출될 수 있습니다. 예금주 필드가 있다면 그 값을 사용하고, 없다면 수령인 문자열은 제외해 주세요.

🛠️ 수정 제안
-            accountInfo = if (this.paymentInfo.bank != null && this.paymentInfo.accountNumber != null) {
-                "${this.paymentInfo.bank} ${this.paymentInfo.accountNumber} ${this.shippingInfo.receiver}"
-            } else {
-                "-"
-            },
+            accountInfo = if (this.paymentInfo.bank != null && this.paymentInfo.accountNumber != null) {
+                "${this.paymentInfo.bank} ${this.paymentInfo.accountNumber}"
+            } else {
+                "-"
+            },
📝 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.

Suggested change
paymentInfo = PaymentInfoUiModel(
shippingFee = this.paymentInfo.shippingFee,
totalAmount = this.paymentInfo.totalAmount,
depositStatus = this.paymentInfo.depositStatus,
accountInfo = if (this.paymentInfo.bank != null && this.paymentInfo.accountNumber != null) {
"${this.paymentInfo.bank} ${this.paymentInfo.accountNumber} ${this.shippingInfo.receiver}"
} else {
"-"
},
paymentInfo = PaymentInfoUiModel(
shippingFee = this.paymentInfo.shippingFee,
totalAmount = this.paymentInfo.totalAmount,
depositStatus = this.paymentInfo.depositStatus,
accountInfo = if (this.paymentInfo.bank != null && this.paymentInfo.accountNumber != null) {
"${this.paymentInfo.bank} ${this.paymentInfo.accountNumber}"
} else {
"-"
},
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/participant/model/ParticipantDetailUiModel.kt`
around lines 45 - 53, The accountInfo currently appends shippingInfo.receiver
which can expose the wrong name; update the PaymentInfoUiModel.accountInfo
construction so that when this.paymentInfo.bank and
this.paymentInfo.accountNumber are present you include the actual depositor
field (e.g., this.paymentInfo.depositName or this.paymentInfo.depositor) if
non-null/non-blank, otherwise do NOT append shippingInfo.receiver and just
render "bank accountNumber"; modify the logic in the PaymentInfoUiModel mapping
inside ParticipantDetailUiModel (the paymentInfo -> accountInfo block) to prefer
the depositor field and omit shippingInfo.receiver when depositor is absent.

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: 2

🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/poti/android/presentation/history/participant/ParticipantViewModel.kt`:
- Around line 126-133: patchSubmitDeposit currently just sets overlayState to
ParticipantDetailOverlayState.None without calling the API or handling failures;
wrap the API call in launchScope inside a try/catch, set overlayState to a
loading state before the call, call the deposit confirmation API (use the same
function names you already have for network calls), on success updateState to
overlayState = ParticipantDetailOverlayState.None and refresh participant data,
and on failure set overlayState to an error/failed state (or retain the previous
overlayState) and emit a user-facing error (e.g., via a snackbar/toast event or
error field) so failures are surfaced and state is not incorrectly cleared;
ensure you reference patchSubmitDeposit, updateState and
ParticipantDetailOverlayState when implementing this.
- Around line 82-124: getParticipantDetail uses launchScope without providing an
onError handler so failures are swallowed; update launchScope call in
getParticipantDetail to provide an onError callback that updates
participantDetailState to an error state (e.g., ApiState.Error or a suitable
ApiState failure variant) via updateState, passing the exception/message so the
UI can show failure feedback; ensure you reference getParticipantDetail,
launchScope, updateState and participantDetailState when implementing the
change.
🧹 Nitpick comments (4)
app/src/main/java/com/poti/android/presentation/history/participant/model/ParticipantDetailOverlayState.kt (2)

3-15: Sealed interface 패턴이 잘 적용되어 있어요!

오버레이 상태를 sealed interface로 정의하여 when 표현식에서 컴파일 타임 exhaustiveness 검사를 받을 수 있도록 한 점이 좋습니다. data object 사용도 Kotlin 1.9+ 권장 사항에 부합해요.

다만, Jetpack Compose에서 불필요한 리컴포지션을 방지하기 위해 @Immutable 어노테이션 추가를 고려해 주세요.

♻️ 리컴포지션 최적화를 위한 제안
 package com.poti.android.presentation.history.participant.model
 
+import androidx.compose.runtime.Immutable
+
+@Immutable
 sealed interface ParticipantDetailOverlayState {
     data object None : ParticipantDetailOverlayState
 
     data object DepositBottomSheet : ParticipantDetailOverlayState
 
     data object DeliveryConfirmModal : ParticipantDetailOverlayState
 
+    `@Immutable`
     data class DeliveryReviewModal(
         val recruiterName: String,
         val recruiterProfileUrl: String,
         val partnerRating: String,
     ) : ParticipantDetailOverlayState
 }

10-14: partnerRating 타입을 Double 또는 Float로 변경하는 것을 권장드려요.

partnerRating이 평점을 나타내는 값이라면, String 대신 숫자 타입을 사용하는 것이 타입 안전성과 연산 측면에서 더 적합해요. UI에서 포맷팅이 필요한 경우 표시 시점에 변환하는 것이 좋습니다.

♻️ 타입 변경 제안
     data class DeliveryReviewModal(
         val recruiterName: String,
         val recruiterProfileUrl: String,
-        val partnerRating: String,
+        val partnerRating: Double,
     ) : ParticipantDetailOverlayState
app/src/main/java/com/poti/android/presentation/history/participant/ParticipantViewModel.kt (2)

35-80: processIntent가 모든 Intent를 처리하고 있어 좋아요.

when 표현식이 exhaustive하게 모든 케이스를 처리하고 있습니다. 이전 리뷰에서 지적된 TODO() 플레이스홀더 문제도 해결되었네요.

다만, TODO 주석이 여러 곳에 남아있는데 (Line 57-58, 71, 76, 130-131), PR 체크리스트에 "바텀시트 연결"과 "모달 연결"이 미완료로 표시되어 있으니 후속 작업에서 구현될 것으로 보여요.

TODO 항목들의 구현을 도와드릴까요? 필요하시면 API 연동 코드 템플릿을 제공해 드릴 수 있어요.


7-8: 일부 필드는 Domain 모델을 직접 사용하고 있어요.

ParticipantDetailUiModel에서 partySummary: PartySummarymemberPayments: List<MemberPayment>가 Domain 모델을 직접 사용하고 있는데, paymentInfoshippingInfoPaymentInfoUiModel, ParticipantShippingUiModel 같은 전용 UI 모델로 변환하고 있어서 일관성이 떨어져요. Domain 모델이 순수 Kotlin이라 기술적 문제는 없지만, 계층 분리를 더 명확하게 하려면 PartySummaryUiModel, MemberPaymentUiModel 같은 UI 모델로 변환하는 것도 고려해볼 만해요.

Comment on lines +82 to +124
private fun getParticipantDetail(participantId: Long) = launchScope {
updateState {
copy(
participantDetailState = ApiState.Success(
ParticipantDetailUiModel(
participationId = 1,
partyId = 1,
orderNumber = "참여번호 poti-1",
partySummary = PartySummary(
imageUrl = "",
artist = "아이브(아이브)",
title = "러브다이브 위드뮤",
partyStatus = PartyStatusType.CLOSED,
statusMessage = "다른 참여자를 기다리고 있어요",
),
memberPayments = listOf(
MemberPayment(
memberName = "멤버1",
price = 9000,
),
),
paymentInfo = PaymentInfoUiModel(
shippingFee = 9000,
totalAmount = 18000,
depositStatus = ParticipantStatusType.DELIVERED,
accountInfo = "카카오뱅크 3333-19-1234123 이포티",
depositDeadline = "2026-01-01 23:50 까지",
),
shippingInfo = ParticipantShippingUiModel(
shippingMethod = "일반택",
deliveryTrackingInfo = "우체국 20203344656423232",
receiver = "이포티",
addressInfo = "이포티\n(01234) 서울특별시 솝트구 다솝로 456\n010-1234-5678",
carrier = null,
trackingNumber = null,
shippingStatus = ParticipantStatusType.DELIVERED,
),
buttonState = ParticipantButtonState.DELIVERY_RECEIVED,
),
),
)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

launchScope 호출 시 에러 핸들링이 누락되어 있어요.

현재 launchScopeonError 콜백을 제공하지 않아, API 호출 실패 시 사용자에게 피드백 없이 실패가 무시됩니다. 실제 API 연동 시에는 에러 상태 처리가 필요해요.

🔧 에러 핸들링 추가 제안
-    private fun getParticipantDetail(participantId: Long) = launchScope {
+    private fun getParticipantDetail(participantId: Long) = launchScope(
+        onError = { throwable ->
+            updateState { copy(participantDetailState = ApiState.Error(throwable)) }
+        }
+    ) {
         updateState {
             copy(
                 participantDetailState = ApiState.Success(
                     // ... 
                 ),
             )
         }
     }
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/participant/ParticipantViewModel.kt`
around lines 82 - 124, getParticipantDetail uses launchScope without providing
an onError handler so failures are swallowed; update launchScope call in
getParticipantDetail to provide an onError callback that updates
participantDetailState to an error state (e.g., ApiState.Error or a suitable
ApiState failure variant) via updateState, passing the exception/message so the
UI can show failure feedback; ensure you reference getParticipantDetail,
launchScope, updateState and participantDetailState when implementing the
change.

Comment on lines +126 to +133
private fun patchSubmitDeposit(
depositor: String,
depositTime: String,
) = launchScope {
// TODO: 입금 확인 요청 API 호출
// 성공 시 overlayState = None 및 데이터 갱신
updateState { copy(overlayState = ParticipantDetailOverlayState.None) }
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

patchSubmitDeposit 함수도 에러 핸들링이 필요해요.

API 호출이 추가될 예정이므로, 에러 발생 시 사용자에게 적절한 피드백을 제공하고 overlayState를 유지하거나 에러 상태로 전환하는 로직이 필요합니다.

🔧 에러 핸들링 추가 제안
     private fun patchSubmitDeposit(
         depositor: String,
         depositTime: String,
-    ) = launchScope {
+    ) = launchScope(
+        onError = { throwable ->
+            // TODO: 에러 처리 (토스트 또는 에러 상태 표시)
+        }
+    ) {
         // TODO: 입금 확인 요청 API 호출
         // 성공 시 overlayState = None 및 데이터 갱신
         updateState { copy(overlayState = ParticipantDetailOverlayState.None) }
     }
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/poti/android/presentation/history/participant/ParticipantViewModel.kt`
around lines 126 - 133, patchSubmitDeposit currently just sets overlayState to
ParticipantDetailOverlayState.None without calling the API or handling failures;
wrap the API call in launchScope inside a try/catch, set overlayState to a
loading state before the call, call the deposit confirmation API (use the same
function names you already have for network calls), on success updateState to
overlayState = ParticipantDetailOverlayState.None and refresh participant data,
and on failure set overlayState to an error/failed state (or retain the previous
overlayState) and emit a user-facing error (e.g., via a snackbar/toast event or
error field) so failures are surfaced and state is not incorrectly cleared;
ensure you reference patchSubmitDeposit, updateState and
ParticipantDetailOverlayState when implementing this.

@jyvnee jyvnee merged commit abbbbe2 into develop Jan 22, 2026
2 checks passed
@github-project-automation github-project-automation bot moved this from To-do to Done in POTI-ANDROID Jan 22, 2026
@jyvnee jyvnee deleted the feat/#88-participant-detail-ui branch January 22, 2026 08:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ 지현 🌟 Feat 새로운 기능 구현 🔎 UI UI 작업

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Feat] 상세 내역 - 참여 UI

2 participants