Skip to content

[Feature] Verdict 및 Mate 1차 개발 범위 구현#86

Merged
UiHyeon-Kim merged 114 commits intodevelopfrom
feature/verdict_base
Mar 30, 2026
Merged

[Feature] Verdict 및 Mate 1차 개발 범위 구현#86
UiHyeon-Kim merged 114 commits intodevelopfrom
feature/verdict_base

Conversation

@UiHyeon-Kim
Copy link
Copy Markdown
Member

@UiHyeon-Kim UiHyeon-Kim commented Mar 16, 2026

✨ 주요 변경 사항

  • 친구 초대(초대 코드 입력/공유) 및 수신된 친구 요청 확인·처리
  • 심판 요청·심판 제출 흐름 및 판결 완료 화면 추가
  • 심판 전용 탭, 심판 목록·요청 화면 및 관련 UI 컴포넌트 추가
  • 초대 코드 SMS 전송 및 클립보드 복사 UI 제공

  • 초대 코드 검증·캐시 및 입력 다이얼로그 개선
  • 삭제/판정/요청 흐름을 위한 다이얼로그·하단시트·스낵바 UX 개선
  • 디자인·아이콘·색상·레이아웃 보완 (모달·버튼·테마 보정)

✅ 체크리스트

  • 🌱 merge 브랜치 확인
  • 🛠️ 빌드 성공

🔍 중점 리뷰 사항

  • 비고 내용 제외 우선 리뷰 부탁드립니다.

비고

  • VerdictScreen에 pullToRefresh 적용. 다음 회의 시 실제 적용 여부 및 적용시 디자인 논의 필요
  • 친구 삭제 백엔드서 기능 미구현으로 Todo 상태로 남아있음
    • 우선 병합 후 기능 구현 후 PR 예정

📸 스크린샷

e.g. <img width="45%" src="링크" />

image image image image image image image image

UiHyeon-Kim and others added 30 commits February 15, 2026 17:16
- `BottomNavItem`에 판결(Verdict) 탭을 추가합니다.
- 판결 탭의 활성 및 비활성 상태 아이콘을 추가합니다.
- `LazyColumn`을 사용하여 심판 화면의 전체적인 레이아웃을 구성합니다.
- 상단 앱바에 '심판' 타이틀과 '배심원 목록'으로 이동하는 아이콘 버튼을 추가합니다.
- '판결 요청'을 처리하기 위한 `VerdictNewRequestCard` 컴포넌트를 추가합니다.
- '내가 보낸 심판 요청' 목록 섹션을 구현하고, 필터링을 위한 `PickleChip`과 목록 아이템을 추가합니다.
- 심판 생성 화면으로 이동하는 FAB(Floating Action Button)을 추가합니다.
- `EmptyVerdict` 컴포넌트를 추가하여 요청 목록이 비어있을 때의 UI를 정의합니다.
- 관련 아이콘 리소스(`ic_verdict_juror`, `ic_common_new`)를 추가합니다.
- 판결(verdict) 화면 관련 일러스트 및 아이콘 추가
  - `illust_verdict_gavel.xml` (의사봉)
  - `illust_verdict_balances.xml` (저울)
  - `illust_verdict_empty_juror.xml` (빈 배심원)
  - `illust_verdict_news.xml` (뉴스)
  - `ic_verdict_arrow_right.xml` (오른쪽 화살표)
  - `ic_verdict_arrow_cross.xml` (교차 화살표)
  - `illust_verdict_guilty.png` (유죄)
- 공용(common) 아이콘 추가
  - `ic_appbar_dot_menu.xml` (점 메뉴)
  - `ic_common_open_double_quotation.xml` (여는 따옴표)
  - `ic_common_close_double_quotation.xml` (닫는 따옴표)
판결과 관련된 새로운 데이터 클래스와 enum을 `domain` 모듈에 추가합니다.

- `Verdict`: 판결 정보 모델
- `JurorInfo`: 배심원 정보 모델
- `JurorRelationStats`: 배심원 관계 통계 모델
- `VerdictEnums`: `VerdictStatus`와 `VerdictResult` enum 클래스
- 기존의 정적 레이아웃을 `Scaffold`와 `LazyColumn`을 사용한 동적 리스트 구조로 리팩토링했습니다.
- '내 심판'과 '내 판결'을 볼 수 있는 탭(Tab) UI를 추가했습니다.
- '전체', '대기중/보류', '완료' 상태에 따라 목록을 필터링하는 기능을 추가했습니다.
- 심판 목록이 비어있을 때 표시되는 Empty View를 구현했습니다.
- 신규 컴포넌트를 추가했습니다: `VerdictListItem`, `VerdictTabs`, `VerdictNewInfoBanner`
- `VerdictViewModel`에 상태 관리 로직과 더미 데이터 생성을 추가하여 UI와 데이터를 연동했습니다.
- 사용되지 않는 디자인을 삭제했습니다.
- `VerdictUiModel` 추가
- `VerdictCounts` 추가
- `VerdictScreen`과 하위 컴포저블(`VerdictTabs`, `VerdictListItem`)의 UI 상태를 `VerdictUiState`에서 개별 속성으로 분리하여 전달하도록 변경했습니다.
- `VerdictListItem`의 `key`로 `id`를 지정하여 리스트 성능을 최적화했습니다.
- 심판/판결 상태에 따라 '대기'와 '완료' 상태 칩의 색상을 다르게 표시하도록 UI를 개선했습니다.
- 화면 전환을 위한 `SideEffect` 처리 로직을 추가했습니다.
- 판결/심판 탭에서 `대기`, `보류` 상태의 아이템을 클릭하면 상세 내용을 볼 수 있는 바텀시트를 추가합니다.
- 아이템 클릭 이벤트 처리를 위해 `onVerdictItemClick` 핸들러를 추가하고 ViewModel에 관련 로직을 구현합니다.
- VerdictUiState의 구조를 개편하여 `judgements`와 `verdicts` 각각의 상태를 관리하도록 변경합니다.
- `완료` 상태의 아이템 클릭 시 판결 결과 화면으로 이동하도록 수정합니다.
- `verdict/result` 경로에 `VerdictResultScreen`을 추가합니다.
- 화면 내용은 임시 텍스트로 구성되어 있습니다.
- 심판(Verdict) 화면에 있던 '심판 만들기' 플로팅 액션 버튼(FAB)을 제거합니다.
- 메인(Main) 화면의 심판 탭에서만 '심판 만들기' 플로팅 액션 버튼이 보이도록 로직을 추가합니다.
- 심판 화면의 배심원 목록 아이콘 클릭 이벤트를 ViewModel에서 처리하도록 변경합니다.
- `JurorInfo` 데이터 클래스에서 `badgeName`과 `jurorCode` 필드를 제거합니다.
- 변경된 모델에 맞춰 `VerdictViewModel` 및 `VerdictScreen`의 테스트/미리보기 데이터를 수정합니다.
- `VerdictPendingBottomSheetContent.kt` 파일을 삭제합니다.
- `VerdictScreen.kt`에서 사용하지 않는 `PickleBottomSheet` 및 관련 로직을 제거합니다.
…h-2-android into feature/verdict_ui

# Conflicts:
#	presentation/src/main/java/com/smtm/pickle/presentation/main/MainScreen.kt
- `illust_common_confetti.xml` 벡터 드로어블을 추가합니다.
- `illust_verdict_guilty_small.png` 이미지를 추가합니다.
- 심판 목록에서 새로운 소식 배너 관련 코드를 제거합니다.
- `VerdictNewInfoBanner.kt` 파일을 삭제합니다.
- 심판 목록 아이템(`VerdictListItem`)에서 불필요한 파라미터(`selectedTabIndex`, `jurorNickname`)를 제거합니다.
- '대기' 또는 '보류' 상태를 '미완료'로 통일하여 표시합니다.
- 심판 목록 아이템의 그림자(elevation) 값을 8.dp에서 6.dp로 조정합니다.
- 심판 목록 아이템에서 심사위원 닉네임 표시 로직을 삭제합니다.
- `VerdictUiModel`에서 `juror`를 `defendant`로 변경하여 명확성을 높이고, `isNew` 속성을 추가합니다.
- 심판 완료 시 표시될 `VerdictCompletedContent` 컴포저블을 새로 추가합니다.
- 유죄/무죄 판결을 위한 `JudgementDialog` 컴포저블을 새로 추가합니다.
- 소비 내역을 표시하는 `ConsumptionItem` 컴포저블을 새로 추가합니다.
- 심판 대기/완료 상태를 보여주는 `VerdictPendingBottomSheetContent`를 추가합니다.
- 유죄/무죄 판결에 사용되는 `innocent` 및 `guilty` 관련 시맨틱 색상을 수정 및 추가합니다.
- 심판 대상(`selectedVerdictForJudgement`) 상태를 `VerdictUiState`에 추가합니다.
- 심판하기 팝업(`JudgementDialog`) 관련 로직을 `VerdictViewModel`에 구현합니다.
    - 팝업 해제(`onJudgementDialogDismiss`), 심판 제출(`onSubmitJudgement`) 함수를 추가합니다.
- 심판 제출 시, 심판 완료 화면으로 이동하는 로직을 구현합니다.
    - `VerdictEffect.NavigateToCompleted` 이벤트를 정의하고, 네비게이션을 처리합니다.
- 심판 완료 화면을 위한 `VerdictCompletedRoute`를 추가합니다.
- `onVerdictItemClick` 로직을 수정하여 '심판하기' 탭에서 항목 클릭 시 팝업이 뜨도록 변경합니다.
- `VerdictUiModel`의 `juror` 속성명을 `defendant`로 변경합니다.
- `VerdictListItem`에서 사용하지 않는 `selectedTabIndex`와 `jurorNickname` 파라미터를 제거합니다.
`ObserveNicknameUseCase`를 사용하여 `VerdictViewModel`에서 사용자 닉네임을 관찰하고, 이를 `VerdictUiState`에 반영합니다.

이 변경으로 심판 대기 바텀시트에서 현재 사용자와 상대방의 닉네임이 올바르게 표시됩니다.
- `JurorInfo` 도메인 모델의 이름을 `Juror`로 변경하고, 사용하지 않는 `badgeCode` 속성을 제거합니다.
- `JurorRelationStats` 도메인 모델을 삭제합니다.
- `verdict` 패키지 내 `Verdict` 도메인 모델의 `juror` 속성 이름을 `defendant`로 변경하여 역할을 명확히 합니다.
- 프레젠테이션 계층에 `JurorUiModel` 및 `toUiModel` 확장 함수를 추가합니다.
- `VerdictViewModel`에서 도메인 모델을 직접 사용하지 않고 `VerdictUiModel`을 사용하도록 수정합니다.
- `VerdictCompletedContent.kt` 파일의 패키지 경로를 `components`에서 `submit`으로 변경합니다.
- `illust_mypage_gavel`을 `illust_common_gavel`로 변경하여 공통으로 사용될 수 있도록 합니다.
- 해당 리소스를 사용하는 `VerdictCompletedContent`와 `ActivityActionCardRow`의 참조를 업데이트합니다.
- `VerdictPendingListItem`의 `when`문에 `미완료` 상태를 표시하는 `else` 분기를 추가합니다.
- `JudgmentResultChip` 컴포저블의 가시성을 `private`으로 변경하고, 고정된 크기 대신 내부 패딩을 사용하도록 수정합니다.
- 심판이 없을 때 표시되는 `illust_verdict_empty_juror` 일러스트를 추가합니다.
- 공통으로 사용되는 복사 아이콘(`ic_common_copy`)을 추가합니다.
- 공통으로 사용되는 소셜 아이콘(`ic_common_social`)을 추가합니다.
- 하단 네비게이션 바에 '심판' 탭을 추가합니다.
- '심판' 탭 아이콘(활성/비활성)을 추가합니다.
- 메이트 신청 화면(`MateRequestScreen`)과 관련 라우트(`MateRequestRoute`)를 추가하고 네비게이션 그래프에 연결합니다.
- 심판 목록 화면에서 메이트 신청 및 심판 상세 화면으로 이동하는 네비게이션 로직을 구현합니다.
- `InviteBottomSheetContent.kt` 파일을 추가하여 배심원 초대 바텀시트의 UI를 구현합니다.
- 초대 코드를 클립보드에 복사하는 기능과 문자로 공유하는 기능을 포함합니다.
- `PickleModal.kt`의 불필요한 패딩을 제거하여 컨텐츠 영역에서 패딩을 직접 관리하도록 수정합니다.
- 심판(Juror) 데이터 클래스를 추가합니다.
- `id`와 `nickname` 필드를 포함합니다.
- `MateRequestScreen`을 추가하여 지인 심사 요청 목록을 표시하는 화면을 구현합니다.
- `MateRequestItem` 컴포저블을 추가하여 각 심사 요청 항목의 UI를 정의합니다.
- 수락 및 거절 버튼을 포함한 기본 레이아웃과 임시 데이터를 사용합니다.
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

이번 PR은 Verdict 및 Mate 관련 새로운 기능들을 구현하는 대규모 변경사항을 포함하고 있습니다. 데이터, 도메인, 프레젠테이션 레이어 전반에 걸쳐 많은 코드가 추가 및 수정되었습니다. 전반적으로 코드는 UseCase, Repository 패턴을 잘 활용하고 있으며, UI 상태 관리를 위해 sealed interface와 StateFlow를 적절히 사용하여 구조적으로 잘 작성되었습니다. DI 설정 또한 올바르게 업데이트되었습니다. 몇 가지 개선점에 대한 의견을 리뷰 코멘트로 남겼습니다.

Comment thread data/src/main/java/com/smtm/pickle/data/common/utils/ApiCall.kt
@gemini-code-assist
Copy link
Copy Markdown

안녕하세요, gemini-code-assist입니다. 한국어로 답변을 요청해주셔서 감사합니다. 앞으로는 한국어로 소통하도록 하겠습니다! 😊

- AuthRepositoryImpl: logout API 호출 실패 여부와 상관없이 로컬 토큰을 삭제하도록 finally 블록 적용
- ApiCall: 에러 메시지 파싱 결과가 null일 경우 기본 안내 문구 제공
- 하드코딩된 UI 텍스트(심판, 판결, 초대 안내 등)를 `strings.xml` 리소스로 대체
- `dialog_cancel`을 `common_cancel`로 변경하여 공통 리소스 명칭 통합
- `EmptyVerdictContent`, `InviteIntroductionDialog`, `JudgementDialog` 등 다수의 컴포넌트에 `stringResource` 적용
- InputInvitationCodeDialog: `InputStateUtils`를 사용하여 초대 코드 유효성 검사 로직 통합
- VerdictViewModel: `async`/`awaitAll`을 활용한 데이터 병렬 로드 및 새로고침 상태 관리 개선
- MateRequestViewModel: 중복 요청 방지 및 로딩 상태(`isLoading`) 관리 추가
- MateRequestItem: 불필요한 텍스트 색상 속성 제거
- JurorListItem: Preview에 배경 설정 추가 및 샘플 데이터 변경 (한글/영문 코드)
- JurorListDeleteMateConfirmDialog: 삭제 확인 버튼 텍스트를 `juror_list_reject`에서 `juror_list_delete_confirm`으로 변경
- strings.xml: `juror_list_reject` 리소스를 `juror_list_delete_confirm`으로 이름 변경
- JurorListScreen: 배심원 아이템 클릭 비활성화 주석(TODO) 추가
- VerdictNavigation: 백스택 이동 로직 포맷팅 수정
- presentation 계층에서 domain의 VerdictType을 직접 참조하지 않도록 VerdictTypeUiModel 추가
- AssignedVerdictUiModel, RequestedVerdictUiModel의 verdictType 타입을 UI 모델로 변경
- VerdictViewModel 및 관련 Screen/Component에서 신규 UI 모델을 사용하도록 수정
- Domain 모델과 UI 모델 간의 상호 변환을 위한 확장 함수(toUiModel, toDomain) 구현
…h-2-android into feature/verdict_base

# Conflicts:
#	presentation/src/main/java/com/smtm/pickle/presentation/ledger/detail/LedgerDetailScreen.kt
#	presentation/src/main/java/com/smtm/pickle/presentation/login/nickname/NicknameScreen.kt
#	presentation/src/main/res/values/strings.xml
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
presentation/src/main/java/com/smtm/pickle/presentation/ledger/detail/LedgerDetailScreen.kt (1)

70-74: ⚠️ Potential issue | 🟡 Minor

성공 안내가 에러 스낵바 스타일로 표시될 가능성이 있어 보여요.

confirmJudgmentRequest 성공 분기에서도 LedgerDetailEffect.ShowSnackBar를 emit하는데(LedgerDetailViewModel.kt Line 119 부근), 이 화면은 ShowSnackBar를 항상 PickleSnackbar.toastError(...)로 렌더링하고 있습니다. 성공/실패를 effect 타입으로 분리하거나 스낵바 스타일 정보를 함께 전달해보시면 UX 혼선을 줄일 수 있습니다.

Also applies to: 90-93

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/ledger/detail/LedgerDetailScreen.kt`
around lines 70 - 74, The screen currently renders all ShowSnackBar effects
using PickleSnackbar.toastError, which can display success messages with an
error style; update the effect handling so the UI can distinguish success vs
error (either by emitting distinct effect types like
LedgerDetailEffect.ShowSuccessSnackBar and LedgerDetailEffect.ShowErrorSnackBar
from LedgerDetailViewModel or by extending LedgerDetailEffect.ShowSnackBar to
carry a style/type flag), then in LedgerDetailScreen.kt switch on that type when
handling LedgerDetailEffect.ShowSnackBar to call PickleSnackbar.toastSuccess for
success and PickleSnackbar.toastError for errors (apply same change to the other
handling block around lines 90–93).
🧹 Nitpick comments (10)
presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/materequest/components/MateRequestItem.kt (1)

43-55: 텍스트 길이에 따라 우측 액션 버튼 영역이 밀릴 수 있어 보여요.

Line 43-53의 nickname, invitationCode에 줄 수 제한이 없어 긴 값에서 레이아웃이 깨질 수 있습니다. 한 줄 + 말줄임을 적용하면 리스트 안정성이 좋아집니다.

제안 드리는 수정 예시
+import androidx.compose.ui.text.style.TextOverflow
@@
             Text(
                 text = nickname,
                 style = PickleTheme.typography.body1Bold,
-                color = PickleTheme.colors.gray800
+                color = PickleTheme.colors.gray800,
+                maxLines = 1,
+                overflow = TextOverflow.Ellipsis
             )
             Text(
                 text = invitationCode,
                 style = PickleTheme.typography.caption1Medium,
-                color = PickleTheme.colors.gray600
+                color = PickleTheme.colors.gray600,
+                maxLines = 1,
+                overflow = TextOverflow.Ellipsis
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/materequest/components/MateRequestItem.kt`
around lines 43 - 55, 닉네임(nickname)과 초대코드(invitationCode) 텍스트에 줄 수 제한이 없어 긴 문자열이
오른쪽 액션 버튼을 밀 수 있으니 MateRequestItem의 두 Text 컴포저블(nickname, invitationCode)에 각각
maxLines=1과 overflow=TextOverflow.Ellipsis를 적용하고 필요하면 style이나 modifier 조정으로 레이아웃
안정성을 확보하세요; TextOverflow 심볼을 임포트하고 Column/Spacer 구조는 그대로 유지하면 됩니다.
presentation/src/main/java/com/smtm/pickle/presentation/ledger/detail/LedgerDetailScreen.kt (1)

186-199: Preview 날짜를 고정값으로 두면 미리보기/스냅샷 안정성이 더 좋아집니다.

LocalDate.now()는 날짜 변경 시점마다 Preview 결과가 달라질 수 있어서, 고정 날짜를 쓰는 쪽을 제안드립니다.

제안 코드
-                    occurredOn = LocalDate.now(),
+                    occurredOn = LocalDate.of(2026, 3, 1),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/ledger/detail/LedgerDetailScreen.kt`
around lines 186 - 199, The preview uses LocalDate.now() which makes
LedgerDetailContentPreview unstable across runs; update the
LedgerDetailContentPreview invocation of LedgerDetailContent (specifically the
LedgerDetailUiState.Success -> LedgerUiModel.occurredOn) to use a fixed
LocalDate (e.g., LocalDate.of(2023, 1, 1) or a shared constant) instead of
LocalDate.now() so previews/snapshots for LedgerDetailContentPreview remain
deterministic.
presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/materequest/MateRequestViewModel.kt (2)

31-32: SharedFlow의 버퍼 설정을 검토해 보시는 건 어떨까요?

현재 기본 설정(replay = 0, extraBufferCapacity = 0)으로는 UI가 collect하지 않는 순간에 emit()이 호출되면 이벤트가 유실되거나 코루틴이 suspend될 수 있습니다.

일회성 이벤트(Snackbar 등)를 안정적으로 전달하려면 extraBufferCapacity를 추가하거나 Channel을 사용하는 방법도 고려해 볼 수 있어요.

🔧 예시 수정안
-    private val _effect = MutableSharedFlow<MateRequestEffect>()
+    private val _effect = MutableSharedFlow<MateRequestEffect>(extraBufferCapacity = 1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/materequest/MateRequestViewModel.kt`
around lines 31 - 32, The MutableSharedFlow _effect in MateRequestViewModel is
using default buffering which can drop or suspend emits for one-time UI events;
update its declaration to provide buffering (e.g., set extraBufferCapacity
and/or onBufferOverflow) or replace it with a Channel/Channel.asFlow to
guarantee delivery for MateRequestEffect events; locate the private val _effect
= MutableSharedFlow<MateRequestEffect>() and the public val effect:
SharedFlow<MateRequestEffect> = _effect.asSharedFlow() and change initialization
to use appropriate buffer parameters (or swap to a Channel<MateRequestEffect>
with .consumeAsFlow()) so Snackbar-like one-off events are not lost or blocking.

40-46: 문자열 리소스 추출을 고려해 보시면 좋을 것 같아요.

"친구 요청을 수락했습니다.", "친구 요청을 거절했습니다." 같은 사용자 메시지가 하드코딩되어 있는데요, 유지보수성과 일관성을 위해 strings.xml로 추출하시면 나중에 텍스트 수정이나 다국어 지원 시 편리해집니다.

커밋 이력에 string resource 정리가 포함되어 있는 것 같으니, 이 작업에 포함하시면 될 것 같아요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/materequest/MateRequestViewModel.kt`
around lines 40 - 46, Extract the two hardcoded messages in MateRequestViewModel
(used in onAcceptClick and onRejectClick calling updateStatus) into strings.xml
and stop passing raw strings from the ViewModel; either change updateStatus to
accept a `@StringRes` id (e.g., successMsgRes: Int) or pass the resolved string
from the View layer, then update onAcceptClick/onRejectClick to use the new
resource-based API and reference the new string resource keys (e.g.,
R.string.mate_request_accepted / R.string.mate_request_rejected) so message text
is maintained in strings.xml for localization and maintenance.
presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictViewModel.kt (3)

170-172: 로딩 실패 시 사용자 피드백 부재

현재 로딩 실패 시 Timber.e로 로그만 남기고 사용자에게는 아무런 피드백이 없어요. Pull-to-Refresh 후 데이터가 갱신되지 않으면 사용자가 원인을 알기 어려울 수 있습니다.

onSubmitJudgement에서는 실패 시 ShowSnackBar 이펙트를 발행하고 있으니, 동일한 패턴을 로딩 실패에도 적용하는 것을 고려해 보시면 좋을 것 같아요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictViewModel.kt`
around lines 170 - 172, The onFailure block in VerdictViewModel.kt currently
only logs errors via Timber.e ("내 판결 로드 실패"); update that failure handling to
also emit the same UI feedback used by onSubmitJudgement by dispatching a
ShowSnackBar effect (or the viewModel's equivalent effect emitter) with a
user-friendly message when loading fails, so replace or augment the Timber.e
call in the .onFailure { e -> ... } handler to both log the error and trigger
ShowSnackBar to inform the user of the load failure.

190-228: 중복된 필터/카운트 로직 통합 가능

calculateAssignedCounts/calculateRequestedCountsfilterAssignedVerdicts/filterRequestedVerdicts 함수들이 거의 동일한 로직을 가지고 있어요. 두 UI 모델이 공통 인터페이스를 구현하거나 제네릭 함수를 사용하면 중복을 줄일 수 있습니다.

현재 상태로도 동작에 문제는 없지만, 향후 필터 조건이 변경될 때 두 곳을 모두 수정해야 하는 점을 고려해 보시면 좋을 것 같아요.

💡 공통 인터페이스 활용 예시
// 공통 인터페이스 정의
interface VerdictItem {
    val verdictType: VerdictTypeUiModel
}

// UI 모델에서 구현
data class AssignedVerdictUiModel(...) : VerdictItem
data class RequestedVerdictUiModel(...) : VerdictItem

// 제네릭 함수로 통합
private fun <T : VerdictItem> calculateCounts(verdicts: List<T>): VerdictCounts {
    return VerdictCounts(
        total = verdicts.size,
        pending = verdicts.count { it.verdictType == VerdictTypeUiModel.Pending },
        completed = verdicts.count { it.verdictType != VerdictTypeUiModel.Pending }
    )
}

private fun <T : VerdictItem> filterVerdicts(
    verdicts: List<T>,
    filterIndex: Int,
): List<T> {
    return when (filterIndex) {
        0 -> verdicts
        1 -> verdicts.filter { it.verdictType == VerdictTypeUiModel.Pending }
        2 -> verdicts.filter { it.verdictType != VerdictTypeUiModel.Pending }
        else -> verdicts
    }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictViewModel.kt`
around lines 190 - 228, Refactor duplicated counting and filtering by
introducing a common interface (e.g., VerdictItem with val verdictType:
VerdictTypeUiModel) and make AssignedVerdictUiModel and RequestedVerdictUiModel
implement it, then replace calculateAssignedCounts/calculateRequestedCounts with
a single generic function calculateCounts<T : VerdictItem>(verdicts: List<T>):
VerdictCounts and replace filterAssignedVerdicts/filterRequestedVerdicts with
filterVerdicts<T : VerdictItem>(verdicts: List<T>, filterIndex: Int): List<T>;
update all call sites in VerdictViewModel to use the new generic functions and
remove the now-redundant specific methods.

162-188: 병렬 로딩 시 중간 상태 불일치 가능성

현재 구조에서 두 async 블록이 각각 완료될 때마다 _uiState.update { it.applyFilters() }를 호출하고 있어요. applyFilters()allRequestedVerdictsallAssignedVerdicts 모두를 참조하기 때문에, 첫 번째 요청이 완료되었을 때 다른 리스트는 아직 이전 데이터(또는 빈 리스트)를 가지고 있을 수 있습니다.

결과적으로 두 요청 사이에 잠시 동안 불일치된 중간 상태가 UI에 표시될 수 있어요.

🔧 두 요청 완료 후 한 번만 필터 적용하는 방식 제안
     fun loadVerdicts() {
         _uiState.update { it.copy(isRefreshing = true) }
 
         viewModelScope.launch {
             val jurorVerdictsAsync = async {
                 getJurorVerdictsUseCase()
-                    .onSuccess { assignedVerdicts ->
-                        allAssignedVerdicts = assignedVerdicts.map { it.toUiModel() }
-                            .sortedByDescending { it.id }
-                        _uiState.update { it.applyFilters() }
-                    }
-                    .onFailure { e ->
-                        Timber.e(e, "내 판결 로드 실패")
-                    }
             }
             val myVerdictsAsync = async {
                 getMyVerdictsUseCase()
-                    .onSuccess { requestedVerdicts ->
-                        allRequestedVerdicts = requestedVerdicts.map { it.toUiModel() }
-                            .sortedByDescending { it.id }
-                        _uiState.update { it.applyFilters() }
-                    }
-                    .onFailure { e ->
-                        Timber.e(e, "내 심판 로드 실패")
-                    }
             }
-            awaitAll(jurorVerdictsAsync, myVerdictsAsync)
+            val results = awaitAll(jurorVerdictsAsync, myVerdictsAsync)
+            
+            results[0]
+                .onSuccess { assignedVerdicts ->
+                    allAssignedVerdicts = assignedVerdicts.map { it.toUiModel() }
+                        .sortedByDescending { it.id }
+                }
+                .onFailure { e -> Timber.e(e, "내 판결 로드 실패") }
+            
+            results[1]
+                .onSuccess { requestedVerdicts ->
+                    allRequestedVerdicts = requestedVerdicts.map { it.toUiModel() }
+                        .sortedByDescending { it.id }
+                }
+                .onFailure { e -> Timber.e(e, "내 심판 로드 실패") }
+            
+            _uiState.update { it.applyFilters() }
             _uiState.update { it.copy(isRefreshing = false) }
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictViewModel.kt`
around lines 162 - 188, The issue: calling _uiState.update { it.applyFilters() }
inside each async causes transient inconsistent UI because applyFilters() reads
both allRequestedVerdicts and allAssignedVerdicts; fix by removing the per-async
updates and only updating state once after both async tasks complete: in the
viewModelScope.launch block, run getJurorVerdictsUseCase() and
getMyVerdictsUseCase() in async as you have, capture their successful mapped
results into local variables (e.g., jurorResults, myResults) or set
allAssignedVerdicts/allRequestedVerdicts inside the async without triggering
applyFilters, then after awaitAll(jurorVerdictsAsync, myVerdictsAsync) set the
two lists and call _uiState.update { it.applyFilters(); it.copy(isRefreshing =
false) } (or call applyFilters then set isRefreshing) so applyFilters() runs
only once with both lists available.
presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictScreen.kt (2)

119-131: TODO 주석과 임시 하드코딩된 값에 대한 확인입니다.

Line 121-122의 "익명 배심원" 하드코딩은 TODO 주석으로 서버 API 변경 대기 중임을 명시해 두셨네요. 추후 서버에서 jurorNickname이 제공되면 해당 값으로 교체하시고, 하드코딩된 문자열도 string resource로 이동하시면 좋겠습니다.

이 TODO를 추적하기 위한 이슈를 생성해 드릴까요?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictScreen.kt`
around lines 119 - 131, Replace the hardcoded jurorNickname "익명 배심원" in the
VerdictPendingBottomSheetContent call with a runtime value and move the literal
into resources: use selectedRequestedVerdict.jurorNickname (or null) as the
primary source, and fall back to stringResource(R.string.anonymous_juror) when
the server field is absent; add the "anonymous juror" entry to strings.xml and
remove or update the TODO to reference a tracking issue if desired so the code
no longer relies on a literal.

199-207: AppBar 타이틀에 하드코딩된 문자열이 있습니다.

다른 곳에서는 stringResource()를 사용하고 있는데, Line 201의 "심판" 문자열은 하드코딩되어 있습니다. 일관성과 다국어 대응을 위해 string resource로 분리하시는 것을 권장드립니다.

🔧 제안 코드
         Scaffold(
             topBar = {
-                PickleAppBar(title = "심판") {
+                PickleAppBar(title = stringResource(R.string.verdict_title)) {
                     PickleIconButtonWithTouchCustom(
                         iconRes = R.drawable.ic_verdict_juror,
                         onClick = onJurorListClick
                     )
                 }
             }

strings.xml에 해당 리소스가 없다면 추가해 주세요:

<string name="verdict_title">심판</string>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictScreen.kt`
around lines 199 - 207, The PickleAppBar title in VerdictScreen is using a
hardcoded string ("심판"); replace it with a string resource by calling
stringResource(R.string.verdict_title) (or the equivalent localized accessor)
when constructing PickleAppBar in VerdictScreen, and add the corresponding
<string name="verdict_title">심판</string> entry to strings.xml to keep
localization consistent with other screens.
presentation/src/main/java/com/smtm/pickle/presentation/login/nickname/components/InviteIntroductionDialog.kt (1)

38-42: Preview 어노테이션의 들여쓰기가 일관되지 않습니다.

Line 38에서 @Preview 어노테이션 앞에 불필요한 공백이 포함되어 있습니다. 코드 일관성을 위해 들여쓰기를 정리하시면 좋겠습니다.

🔧 제안 코드
-                `@Preview`(
+@Preview(
     name = "InviteIntroductionDialog",
     showBackground = true,
     widthDp = 360,
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/login/nickname/components/InviteIntroductionDialog.kt`
around lines 38 - 42, The `@Preview` annotation before the
InviteIntroductionDialog preview has inconsistent indentation (an extra leading
space); open the InviteIntroductionDialog composable preview block and remove
the stray leading space so the `@Preview` line aligns with surrounding annotations
and definition, ensuring consistent indentation for the `@Preview`(...) block and
the InviteIntroductionDialog declaration.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@presentation/src/main/java/com/smtm/pickle/presentation/login/nickname/NicknameScreen.kt`:
- Around line 99-104: The sendSms call inside ShareInvitationCodeDialog
currently invokes ContextExt.sendSms which swallows exceptions and returns Unit,
so viewModel.showWelcomeDialog() is always called even when SMS intent fails;
change ContextExt.sendSms (or add a new sendSmsForResult) to return a boolean
indicating success/failure (catching exceptions and returning false on failure),
then update the ShareInvitationCodeDialog usage: call context.sendSms(...) and
only call viewModel.showWelcomeDialog() when the boolean is true; on false, keep
the dialog open and show user feedback (Toast/Snackbar) to indicate the send
failed and allow retry. Ensure you reference ContextExt.sendSms (or new
sendSmsForResult) and ShareInvitationCodeDialog / viewModel.showWelcomeDialog in
the change.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/ui/dialog/InputInvitationCodeDialog.kt`:
- Around line 33-52: displayError computes from invitationCodeErrorMessage when
userHasEdited is false, but the code resets userHasEdited = false just before
calling onCompleteClick, which can cause a prior server error to reappear
immediately; change the flow in the PickleDialog confirm handler (around the
onConfirmClick block) so you either (a) do not set userHasEdited = false until
after the parent/server error has been cleared/updated, or (b) invoke a parent
callback to clear invitationCodeErrorMessage (e.g., onClearInvitationError)
before calling onCompleteClick(invitationCode); update references to
displayError, userHasEdited, invitationCodeErrorMessage and onCompleteClick
accordingly.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/JurorListScreen.kt`:
- Around line 156-168: Replace the deprecated V1 components (PickleAppBar,
NavigationItem.Back and the inline PickleIconButtonWithTouchCustom) with the
Named Preset PickleBackAppBar: call PickleBackAppBar passing title =
stringResource(R.string.juror_list_title), onNavigateBack = onNavigateBack, and
provide the actions list containing a PickleAppBarAction.Icon that uses iconRes
= R.drawable.ic_verdict_mate_request, onClick = onAddJuryClick and tint =
PickleTheme.colors.gray700; remove references to NavigationItem.Back and the old
PickleIconButtonWithTouchCustom in this screen.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/materequest/components/MateRequestItem.kt`:
- Around line 83-91: The fixed Modifier.size(45.dp, 32.dp) on the Button can
cause label clipping for large accessibility font sizes or long translations;
replace it with a variable-width + minimum-height modifier such as
Modifier.heightIn(min = 32.dp) (optionally combined with widthIn(min = 45.dp) if
a minimum width is required) so the Button in MateRequestItem can expand
horizontally while preserving a minimum height; keep the existing shape,
contentPadding and colors (i.e., update the Button call to remove Modifier.size
and use Modifier.heightIn/widthIn as appropriate).

---

Outside diff comments:
In
`@presentation/src/main/java/com/smtm/pickle/presentation/ledger/detail/LedgerDetailScreen.kt`:
- Around line 70-74: The screen currently renders all ShowSnackBar effects using
PickleSnackbar.toastError, which can display success messages with an error
style; update the effect handling so the UI can distinguish success vs error
(either by emitting distinct effect types like
LedgerDetailEffect.ShowSuccessSnackBar and LedgerDetailEffect.ShowErrorSnackBar
from LedgerDetailViewModel or by extending LedgerDetailEffect.ShowSnackBar to
carry a style/type flag), then in LedgerDetailScreen.kt switch on that type when
handling LedgerDetailEffect.ShowSnackBar to call PickleSnackbar.toastSuccess for
success and PickleSnackbar.toastError for errors (apply same change to the other
handling block around lines 90–93).

---

Nitpick comments:
In
`@presentation/src/main/java/com/smtm/pickle/presentation/ledger/detail/LedgerDetailScreen.kt`:
- Around line 186-199: The preview uses LocalDate.now() which makes
LedgerDetailContentPreview unstable across runs; update the
LedgerDetailContentPreview invocation of LedgerDetailContent (specifically the
LedgerDetailUiState.Success -> LedgerUiModel.occurredOn) to use a fixed
LocalDate (e.g., LocalDate.of(2023, 1, 1) or a shared constant) instead of
LocalDate.now() so previews/snapshots for LedgerDetailContentPreview remain
deterministic.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/login/nickname/components/InviteIntroductionDialog.kt`:
- Around line 38-42: The `@Preview` annotation before the InviteIntroductionDialog
preview has inconsistent indentation (an extra leading space); open the
InviteIntroductionDialog composable preview block and remove the stray leading
space so the `@Preview` line aligns with surrounding annotations and definition,
ensuring consistent indentation for the `@Preview`(...) block and the
InviteIntroductionDialog declaration.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/materequest/components/MateRequestItem.kt`:
- Around line 43-55: 닉네임(nickname)과 초대코드(invitationCode) 텍스트에 줄 수 제한이 없어 긴 문자열이
오른쪽 액션 버튼을 밀 수 있으니 MateRequestItem의 두 Text 컴포저블(nickname, invitationCode)에 각각
maxLines=1과 overflow=TextOverflow.Ellipsis를 적용하고 필요하면 style이나 modifier 조정으로 레이아웃
안정성을 확보하세요; TextOverflow 심볼을 임포트하고 Column/Spacer 구조는 그대로 유지하면 됩니다.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/materequest/MateRequestViewModel.kt`:
- Around line 31-32: The MutableSharedFlow _effect in MateRequestViewModel is
using default buffering which can drop or suspend emits for one-time UI events;
update its declaration to provide buffering (e.g., set extraBufferCapacity
and/or onBufferOverflow) or replace it with a Channel/Channel.asFlow to
guarantee delivery for MateRequestEffect events; locate the private val _effect
= MutableSharedFlow<MateRequestEffect>() and the public val effect:
SharedFlow<MateRequestEffect> = _effect.asSharedFlow() and change initialization
to use appropriate buffer parameters (or swap to a Channel<MateRequestEffect>
with .consumeAsFlow()) so Snackbar-like one-off events are not lost or blocking.
- Around line 40-46: Extract the two hardcoded messages in MateRequestViewModel
(used in onAcceptClick and onRejectClick calling updateStatus) into strings.xml
and stop passing raw strings from the ViewModel; either change updateStatus to
accept a `@StringRes` id (e.g., successMsgRes: Int) or pass the resolved string
from the View layer, then update onAcceptClick/onRejectClick to use the new
resource-based API and reference the new string resource keys (e.g.,
R.string.mate_request_accepted / R.string.mate_request_rejected) so message text
is maintained in strings.xml for localization and maintenance.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictScreen.kt`:
- Around line 119-131: Replace the hardcoded jurorNickname "익명 배심원" in the
VerdictPendingBottomSheetContent call with a runtime value and move the literal
into resources: use selectedRequestedVerdict.jurorNickname (or null) as the
primary source, and fall back to stringResource(R.string.anonymous_juror) when
the server field is absent; add the "anonymous juror" entry to strings.xml and
remove or update the TODO to reference a tracking issue if desired so the code
no longer relies on a literal.
- Around line 199-207: The PickleAppBar title in VerdictScreen is using a
hardcoded string ("심판"); replace it with a string resource by calling
stringResource(R.string.verdict_title) (or the equivalent localized accessor)
when constructing PickleAppBar in VerdictScreen, and add the corresponding
<string name="verdict_title">심판</string> entry to strings.xml to keep
localization consistent with other screens.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictViewModel.kt`:
- Around line 170-172: The onFailure block in VerdictViewModel.kt currently only
logs errors via Timber.e ("내 판결 로드 실패"); update that failure handling to also
emit the same UI feedback used by onSubmitJudgement by dispatching a
ShowSnackBar effect (or the viewModel's equivalent effect emitter) with a
user-friendly message when loading fails, so replace or augment the Timber.e
call in the .onFailure { e -> ... } handler to both log the error and trigger
ShowSnackBar to inform the user of the load failure.
- Around line 190-228: Refactor duplicated counting and filtering by introducing
a common interface (e.g., VerdictItem with val verdictType: VerdictTypeUiModel)
and make AssignedVerdictUiModel and RequestedVerdictUiModel implement it, then
replace calculateAssignedCounts/calculateRequestedCounts with a single generic
function calculateCounts<T : VerdictItem>(verdicts: List<T>): VerdictCounts and
replace filterAssignedVerdicts/filterRequestedVerdicts with filterVerdicts<T :
VerdictItem>(verdicts: List<T>, filterIndex: Int): List<T>; update all call
sites in VerdictViewModel to use the new generic functions and remove the
now-redundant specific methods.
- Around line 162-188: The issue: calling _uiState.update { it.applyFilters() }
inside each async causes transient inconsistent UI because applyFilters() reads
both allRequestedVerdicts and allAssignedVerdicts; fix by removing the per-async
updates and only updating state once after both async tasks complete: in the
viewModelScope.launch block, run getJurorVerdictsUseCase() and
getMyVerdictsUseCase() in async as you have, capture their successful mapped
results into local variables (e.g., jurorResults, myResults) or set
allAssignedVerdicts/allRequestedVerdicts inside the async without triggering
applyFilters, then after awaitAll(jurorVerdictsAsync, myVerdictsAsync) set the
two lists and call _uiState.update { it.applyFilters(); it.copy(isRefreshing =
false) } (or call applyFilters then set isRefreshing) so applyFilters() runs
only once with both lists available.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: df48b0d6-6a77-40ec-8a66-4b6ae9d55a56

📥 Commits

Reviewing files that changed from the base of the PR and between 5c9e4bf and 023ce6b.

📒 Files selected for processing (26)
  • data/src/main/java/com/smtm/pickle/data/common/utils/ApiCall.kt
  • data/src/main/java/com/smtm/pickle/data/repository/AuthRepositoryImpl.kt
  • data/src/main/java/com/smtm/pickle/data/repository/MateRepositoryImpl.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/designsystem/theme/dimension/Dimensions.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/ledger/detail/LedgerDetailScreen.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/ledger/detail/component/LedgerDetailJudgmentRequestDialog.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/login/nickname/NicknameScreen.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/login/nickname/components/InviteIntroductionDialog.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/ui/dialog/InputInvitationCodeDialog.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictScreen.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictViewModel.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/components/EmptyVerdictContent.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/components/JudgementDialog.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/components/VerdictListItem.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/components/VerdictPendingBottomSheetContent.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/JurorListScreen.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/components/JurorListDeleteMateConfirmDialog.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/components/JurorListInputInviteCodeDialog.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/components/JurorListItem.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/materequest/MateRequestViewModel.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/materequest/components/MateRequestItem.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/model/AssignedVerdictUiModel.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/model/RequestedVerdictUiModel.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/model/VerdictTypeUiModel.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/navigation/VerdictNavigation.kt
  • presentation/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (7)
  • presentation/src/main/java/com/smtm/pickle/presentation/designsystem/theme/dimension/Dimensions.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/components/JurorListDeleteMateConfirmDialog.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/components/EmptyVerdictContent.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/model/AssignedVerdictUiModel.kt
  • data/src/main/java/com/smtm/pickle/data/repository/MateRepositoryImpl.kt
  • data/src/main/java/com/smtm/pickle/data/repository/AuthRepositoryImpl.kt
  • data/src/main/java/com/smtm/pickle/data/common/utils/ApiCall.kt

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.

🧹 Nitpick comments (2)
presentation/src/main/java/com/smtm/pickle/presentation/ledger/detail/LedgerDetailScreen.kt (2)

181-183: Error 상태 UI 구현을 고려해보시면 좋겠습니다.

현재 LedgerDetailUiState.Error 케이스가 빈 블록으로 처리되어 있습니다. 사용자가 오류 상황을 인지하지 못하고 빈 화면만 보게 될 수 있으므로, 오류 메시지와 함께 재시도 버튼을 제공하는 UI를 구현하시면 사용자 경험이 개선될 것 같습니다.

이 부분에 대해 Error 상태 UI 컴포넌트를 구현하는 것을 도와드릴까요? 혹은 별도 이슈로 트래킹하도록 생성해드릴 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/ledger/detail/LedgerDetailScreen.kt`
around lines 181 - 183, Implement a visible Error UI for the LedgerDetailScreen
to replace the empty LedgerDetailUiState.Error branch: inside the when handling
LedgerDetailUiState (the LedgerDetailScreen composable), render an error
composable that displays the error message and a retry action (e.g., a Retry
button that calls the existing load/retry function or dispatches the same event
used to fetch details), and ensure it uses the error payload from
LedgerDetailUiState.Error (or the ViewModel state) so users see context;
reference LedgerDetailUiState.Error, LedgerDetailScreen, and the ViewModel
event/method that triggers data reload when wiring the Retry action.

188-211: Preview 컴포저블 추가가 좋습니다.

개발 시 UI 확인에 도움이 되는 Preview를 잘 구성해주셨습니다. LedgerUiModel의 모든 필드를 채워서 실제 화면과 유사한 프리뷰를 볼 수 있습니다.

한 가지 작은 제안으로, LocalDate.now()는 프리뷰를 볼 때마다 다른 날짜가 표시될 수 있으므로, 스크린샷 테스트 등을 고려하신다면 고정된 날짜(예: LocalDate.of(2026, 3, 15))를 사용하시는 것도 좋은 방법입니다. 현재 구현도 개발 프리뷰 용도로는 충분합니다.

🔧 고정 날짜 사용 예시
-                    occurredOn = LocalDate.now(),
+                    occurredOn = LocalDate.of(2026, 3, 15),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/ledger/detail/LedgerDetailScreen.kt`
around lines 188 - 211, The preview uses LocalDate.now() which makes screenshots
vary; in LedgerDetailContentPreview set a fixed date for
LedgerUiModel.occurredOn (e.g., LocalDate.of(2026, 3, 15)) so the preview is
deterministic—update the occurredOn value in the LedgerDetailUiState passed to
LedgerDetailContent inside LedgerDetailContentPreview.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@presentation/src/main/java/com/smtm/pickle/presentation/ledger/detail/LedgerDetailScreen.kt`:
- Around line 181-183: Implement a visible Error UI for the LedgerDetailScreen
to replace the empty LedgerDetailUiState.Error branch: inside the when handling
LedgerDetailUiState (the LedgerDetailScreen composable), render an error
composable that displays the error message and a retry action (e.g., a Retry
button that calls the existing load/retry function or dispatches the same event
used to fetch details), and ensure it uses the error payload from
LedgerDetailUiState.Error (or the ViewModel state) so users see context;
reference LedgerDetailUiState.Error, LedgerDetailScreen, and the ViewModel
event/method that triggers data reload when wiring the Retry action.
- Around line 188-211: The preview uses LocalDate.now() which makes screenshots
vary; in LedgerDetailContentPreview set a fixed date for
LedgerUiModel.occurredOn (e.g., LocalDate.of(2026, 3, 15)) so the preview is
deterministic—update the occurredOn value in the LedgerDetailUiState passed to
LedgerDetailContent inside LedgerDetailContentPreview.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ebc5b74b-80cb-4574-ab20-79a7e0b69d25

📥 Commits

Reviewing files that changed from the base of the PR and between 023ce6b and de91b61.

📒 Files selected for processing (1)
  • presentation/src/main/java/com/smtm/pickle/presentation/ledger/detail/LedgerDetailScreen.kt

- PickleAppBar를 PickleTitleAppBar로 교체
- NavigationItem 대신 onBack 파라미터 사용
- 액션 버튼 정의 방식을 trailing lambda에서 actions 리스트 속성으로 변경
- 고정 size 대신 heightIn 및 defaultMinSize를 적용하여 텍스트 길이에 따라 확장되도록 수정
- inviteMate: 초대 요청 시작 시 InputInvitationCode의 errorMessage를 null로 초기화하여 이전 에러 상태를 제거함
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

♻️ Duplicate comments (1)
presentation/src/main/java/com/smtm/pickle/presentation/login/nickname/NicknameViewModel.kt (1)

68-78: ⚠️ Potential issue | 🟡 Minor

초대 코드 조회 실패도 UI에 전달해주면 좋겠습니다.

현재는 로그만 남아서 사용자가 버튼을 눌렀는데 아무 변화가 없는 것처럼 느낄 수 있어 보여요. 스낵바용 effect나 에러 상태를 함께 내려주면 흐름이 더 분명해질 것 같습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/login/nickname/NicknameViewModel.kt`
around lines 68 - 78, The failure branch in showShareInvitationCodeDialog
currently only logs the error; update the onFailure handler to propagate an
error UI effect/state so the view can show feedback (e.g., snackbar).
Specifically, in NicknameViewModel within showShareInvitationCodeDialog, replace
the onFailure Timber.e(...) only path with code that updates _uiState (or emits
a one-off effect) to include an error state or snackbar message (e.g., add a
NicknameDialogState/Error or a separate ui effect field) while still logging the
exception; keep the success path setting
NicknameDialogState.ShareInvitationCode(code) unchanged.
🧹 Nitpick comments (1)
presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/JurorListScreen.kt (1)

131-143: SnackbarHost 배치 위치 확인이 필요해 보입니다.

현재 SnackbarHost(snackbarState)Scaffold 외부에 배치되어 있습니다. 일반적으로 Scaffold의 snackbarHost 슬롯을 활용하면 콘텐츠 영역과의 패딩 처리 및 FAB과의 위치 조정이 자동으로 이루어져 더 안정적인 UI를 구현할 수 있습니다.

현재 구현이 의도된 것이라면 무시해도 괜찮지만, 스낵바가 다른 UI 요소와 겹치는 문제가 발생한다면 아래와 같이 Scaffold 내부로 이동하는 것을 고려해 보시면 좋겠습니다.

♻️ Scaffold snackbarHost 슬롯 활용 예시
Scaffold(
    topBar = { ... },
    snackbarHost = { SnackbarHost(snackbarState) }
) { paddingValues ->
    // content
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/JurorListScreen.kt`
around lines 131 - 143, The SnackbarHost is currently placed outside the
Scaffold which can cause overlap with content/FAB; move the SnackbarHost into
the Scaffold by using the scaffold's snackbarHost slot (e.g., supply
SnackbarHost(snackbarState) to Scaffold(snackbarHost = { ... }) and remove the
standalone SnackbarHost call, and ensure the Scaffold content lambda receives
paddingValues and passes that padding into JurorListContent (or its container)
so UI padding is preserved; update references around JurorListContent,
snackbarState, and Scaffold accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@presentation/src/main/java/com/smtm/pickle/presentation/login/nickname/NicknameViewModel.kt`:
- Around line 96-103: The onFailure lambda in NicknameViewModel (the .onFailure
{ e -> ... } handler updating _uiState to
NicknameDialogState.InputInvitationCode with a single hardcoded "초대코드를 확인해주세요"
message) should distinguish between client-side invalid-code errors and other
failures: inspect the exception type or error payload (e.g.,
IllegalArgumentException/CustomInvalidCodeException vs
IOException/HttpException/other) and set different dialogState/errorMessage
accordingly (e.g., keep "초대코드를 확인해주세요" for confirmed invalid-code errors; use a
network/server friendly message like "네트워크 오류가 발생했습니다. 다시 시도해 주세요" for IO/HTTP
failures and a generic "오류가 발생했습니다" for unknown exceptions). Ensure you still
log the exception (Timber.e(e)) and update _uiState only with the appropriate
message based on the inspected error.

---

Duplicate comments:
In
`@presentation/src/main/java/com/smtm/pickle/presentation/login/nickname/NicknameViewModel.kt`:
- Around line 68-78: The failure branch in showShareInvitationCodeDialog
currently only logs the error; update the onFailure handler to propagate an
error UI effect/state so the view can show feedback (e.g., snackbar).
Specifically, in NicknameViewModel within showShareInvitationCodeDialog, replace
the onFailure Timber.e(...) only path with code that updates _uiState (or emits
a one-off effect) to include an error state or snackbar message (e.g., add a
NicknameDialogState/Error or a separate ui effect field) while still logging the
exception; keep the success path setting
NicknameDialogState.ShareInvitationCode(code) unchanged.

---

Nitpick comments:
In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/JurorListScreen.kt`:
- Around line 131-143: The SnackbarHost is currently placed outside the Scaffold
which can cause overlap with content/FAB; move the SnackbarHost into the
Scaffold by using the scaffold's snackbarHost slot (e.g., supply
SnackbarHost(snackbarState) to Scaffold(snackbarHost = { ... }) and remove the
standalone SnackbarHost call, and ensure the Scaffold content lambda receives
paddingValues and passes that padding into JurorListContent (or its container)
so UI padding is preserved; update references around JurorListContent,
snackbarState, and Scaffold accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6a35015b-acf7-47ff-ba1a-fb54e2d3ce5f

📥 Commits

Reviewing files that changed from the base of the PR and between de91b61 and 8509844.

📒 Files selected for processing (3)
  • presentation/src/main/java/com/smtm/pickle/presentation/login/nickname/NicknameViewModel.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/JurorListScreen.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/materequest/components/MateRequestItem.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/materequest/components/MateRequestItem.kt

- **Domain/Data**: `DefendantInfo`를 `MateInfo`(`RemoteMate`)로 이름을 변경하여 배심원 및 피고인 정보에 공통으로 사용하도록 개선
- **Domain**: `MyVerdict` 모델에 배심원 정보(`jurorInfo`) 필드 추가
- **Presentation**: `VerdictScreen`에서 익명 처리되었던 배심원 닉네임을 실제 데이터로 표시하도록 수정
- **UI**: `VerdictScreen` 상단 바를 `PickleTitleAppBar`로 교체하여 액션 버튼 구조 개선
- 온보딩, 판결 완료, 메이트 요청 화면에서 기존 PickleAppBar를 PickleTitleAppBar로 교체
- PickleAppBarAction 모델을 사용하여 앱바 내 텍스트 및 아이콘 액션 정의 방식 개선
- 메이트 요청 화면의 내비게이션 처리를 onBack 콜백으로 단순화
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

🧹 Nitpick comments (2)
presentation/src/main/java/com/smtm/pickle/presentation/onboarding/OnboardingScreen.kt (1)

74-82: LGTM! PickleTitleAppBar와 PickleAppBarAction.Text를 활용한 깔끔한 구현입니다.

디자인 시스템의 Named Preset 패턴을 잘 따르고 있으며, 액션 정의 방식도 PickleAppBarAction.Text sealed interface를 올바르게 사용하고 있습니다.

다만 참고 사항으로, PickleAppBarTextAction 컴포넌트(Context snippet 2 참조)가 현재 Modifier.clickable()만 적용하고 있어 터치 영역이 텍스트 크기에 의존합니다. 접근성 가이드라인에서 권장하는 최소 48dp 터치 타겟을 충족하지 못할 수 있습니다.

이 부분은 PickleAppBarTextAction.kt 디자인 시스템 컴포넌트 레벨에서 개선을 검토해 보시면 좋을 것 같습니다. 예를 들어 Modifier.minimumInteractiveComponentSize()를 추가하는 방식이 있습니다.

💡 PickleAppBarTextAction.kt 개선 제안 (별도 파일)

presentation/src/main/java/com/smtm/pickle/presentation/designsystem/components/appbar/component/PickleAppBarTextAction.kt 파일에서:

 `@Composable`
 internal fun PickleAppBarTextAction(
     text: String,
     onClick: () -> Unit,
 ) {
     Text(
         text = text,
-        modifier = Modifier.clickable(onClick = onClick),
+        modifier = Modifier
+            .minimumInteractiveComponentSize()
+            .clickable(onClick = onClick),
         style = PickleTheme.typography.body2Medium,
         color = PickleTheme.colors.gray700,
     )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/onboarding/OnboardingScreen.kt`
around lines 74 - 82, PickleAppBarTextAction's touch target is currently limited
because it only uses Modifier.clickable(); update the component
(PickleAppBarTextAction) to apply Modifier.minimumInteractiveComponentSize()
(and ensure it's combined with existing Modifier.clickable() and padding in the
correct order) so the touch area meets the 48dp accessibility guideline; adjust
the modifier order so minimumInteractiveComponentSize() wraps the
clickable/padding as needed and run UI/accessibility checks to confirm behavior
across usages of PickleAppBarAction.Text in PickleTitleAppBar.
presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictScreen.kt (1)

93-131: BottomSheet 분기 중복을 한 곳으로 모으면 유지보수가 더 쉬워질 것 같아요.

현재 두 분기가 동일한 sheetState와 거의 같은 UI 매핑을 반복하고 있어, 이후 필드 추가/변경 시 누락 가능성이 있습니다. 또한 두 nullable 상태가 동시에 채워지는 케이스를 방어하려면 단일 분기(when)로 합치는 쪽이 안정적입니다.

리팩터링 예시
-    val selectedAssignedVerdict = uiState.selectedAssignedVerdict
-    if (selectedAssignedVerdict != null) {
-        PickleBottomSheet(...) { VerdictPendingBottomSheetContent(...) }
-    }
-
-    val selectedRequestedVerdict = uiState.selectedRequestedVerdict
-    if (selectedRequestedVerdict != null) {
-        PickleBottomSheet(...) { VerdictPendingBottomSheetContent(...) }
-    }
+    val bottomSheetPayload = when {
+        uiState.selectedAssignedVerdict != null -> {
+            // assigned -> payload 매핑
+        }
+        uiState.selectedRequestedVerdict != null -> {
+            // requested -> payload 매핑
+        }
+        else -> null
+    }
+
+    bottomSheetPayload?.let { payload ->
+        PickleBottomSheet(
+            sheetState = sheetState,
+            onDismiss = viewModel::onDismissBottomSheet,
+        ) {
+            VerdictPendingBottomSheetContent(
+                modifier = Modifier,
+                jurorNickname = payload.jurorNickname,
+                defendantNickname = payload.defendantNickname,
+                title = payload.title,
+                category = payload.category,
+                amount = payload.amount,
+                paymentMethod = payload.paymentMethod,
+                verdictType = payload.verdictType,
+            )
+        }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictScreen.kt`
around lines 93 - 131, Merge the two duplicated BottomSheet branches into a
single branch by deriving a single nullable "verdict view model" from
selectedAssignedVerdict and selectedRequestedVerdict (use
selectedAssignedVerdict?.let { ... } ?: selectedRequestedVerdict?.let { ... } or
a when to decide precedence) and then render one PickleBottomSheet with that
derived object's mapped fields; reuse PickleBottomSheet(sheetState, onDismiss =
viewModel::onDismissBottomSheet) and pass jurorNickname, defendantNickname,
title, category, amount, paymentMethod, verdictType from the unified mapping
into VerdictPendingBottomSheetContent; also handle the edge case where both are
non-null explicitly (choose a precedence or short-circuit and log) so only one
sheet ever renders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictScreen.kt`:
- Around line 60-63: The callbacks onNavigateVerdictResult and
onNavigateJurorDetail are declared to accept a Long but their callers in the
MainScreen are currently parameterless lambdas; update those call sites to
accept a parameter (e.g., { id -> ... } and { jurorId -> ... }) so they match
the declarations and the existing calls that pass effect.id
(onNavigateVerdictResult(effect.id), onNavigateJurorDetail(effect.id)); inside
the new lambdas call rootNavController.navigate(VerdictResultRoute) and
rootNavController.navigate(JurorDetailRoute) (use the id if needed) similar to
how onNavigateVerdictCompleted is handled.

---

Nitpick comments:
In
`@presentation/src/main/java/com/smtm/pickle/presentation/onboarding/OnboardingScreen.kt`:
- Around line 74-82: PickleAppBarTextAction's touch target is currently limited
because it only uses Modifier.clickable(); update the component
(PickleAppBarTextAction) to apply Modifier.minimumInteractiveComponentSize()
(and ensure it's combined with existing Modifier.clickable() and padding in the
correct order) so the touch area meets the 48dp accessibility guideline; adjust
the modifier order so minimumInteractiveComponentSize() wraps the
clickable/padding as needed and run UI/accessibility checks to confirm behavior
across usages of PickleAppBarAction.Text in PickleTitleAppBar.

In
`@presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictScreen.kt`:
- Around line 93-131: Merge the two duplicated BottomSheet branches into a
single branch by deriving a single nullable "verdict view model" from
selectedAssignedVerdict and selectedRequestedVerdict (use
selectedAssignedVerdict?.let { ... } ?: selectedRequestedVerdict?.let { ... } or
a when to decide precedence) and then render one PickleBottomSheet with that
derived object's mapped fields; reuse PickleBottomSheet(sheetState, onDismiss =
viewModel::onDismissBottomSheet) and pass jurorNickname, defendantNickname,
title, category, amount, paymentMethod, verdictType from the unified mapping
into VerdictPendingBottomSheetContent; also handle the edge case where both are
non-null explicitly (choose a precedence or short-circuit and log) so only one
sheet ever renders.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 968da60d-f1c6-42c5-bfb5-48c39313a1e4

📥 Commits

Reviewing files that changed from the base of the PR and between 8509844 and a933322.

📒 Files selected for processing (11)
  • data/src/main/java/com/smtm/pickle/data/mapper/VerdictMapper.kt
  • data/src/main/java/com/smtm/pickle/data/source/remote/model/verdict/RemoteJurorVerdict.kt
  • data/src/main/java/com/smtm/pickle/data/source/remote/model/verdict/RemoteMate.kt
  • data/src/main/java/com/smtm/pickle/data/source/remote/model/verdict/RemoteMyVerdict.kt
  • domain/src/main/java/com/smtm/pickle/domain/model/verdict/Verdict.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/onboarding/OnboardingScreen.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/VerdictScreen.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/complete/VerdictCompletedContent.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/materequest/MateRequestScreen.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/model/MateUiModel.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/model/RequestedVerdictUiModel.kt
✅ Files skipped from review due to trivial changes (2)
  • data/src/main/java/com/smtm/pickle/data/source/remote/model/verdict/RemoteMate.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/model/MateUiModel.kt
🚧 Files skipped from review as they are similar to previous changes (7)
  • data/src/main/java/com/smtm/pickle/data/source/remote/model/verdict/RemoteMyVerdict.kt
  • data/src/main/java/com/smtm/pickle/data/source/remote/model/verdict/RemoteJurorVerdict.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/model/RequestedVerdictUiModel.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/complete/VerdictCompletedContent.kt
  • presentation/src/main/java/com/smtm/pickle/presentation/verdict/jurorlist/materequest/MateRequestScreen.kt
  • domain/src/main/java/com/smtm/pickle/domain/model/verdict/Verdict.kt
  • data/src/main/java/com/smtm/pickle/data/mapper/VerdictMapper.kt

- VerdictEffect: NavigateToResult, NavigateToJurorDetail을 data object로 변경
- VerdictScreen: navigation 콜백(onNavigateVerdictResult, onNavigateJurorDetail)에서 ID 파라미터 제거
@UiHyeon-Kim UiHyeon-Kim added the 리뷰 중 코드 리뷰 진행 상태 label Mar 22, 2026
Copy link
Copy Markdown
Collaborator

@Jooman-Lee Jooman-Lee left a comment

Choose a reason for hiding this comment

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

고생하셨습니다!!

크리티컬한 이슈나 개선사항은 없어보이네요 ㅎㅎ

큰 작업하시느라 정말 수고 많으셨습니다!

@Jooman-Lee Jooman-Lee added 병합 대기 병합이 가능한 상태 and removed 리뷰 중 코드 리뷰 진행 상태 labels Mar 25, 2026
- GetNicknameUseCase 삭제
- NicknameSettingViewModel: 초기 닉네임 로드 시 SyncUserUseCase를 사용하도록 수정
- domain: `MateInfo`를 `VerdictMate`로 변경 및 주석 수정
- data: `RemoteMate`를 `RemoteVerdictMate`로 변경하고 관련 Mapper 및 DTO 수정
- presentation: `MateUiModel` 변환 함수에서 변경된 모델명 반영
- 구성 요소의 명칭을 Content에서 Screen으로 일관성 있게 변경
- VerdictNavigation 및 Preview에서의 참조 수정
- JurorListScreen: 삭제 도트 메뉴 주석 처리 및 관련 TODO 추가
- JurorListViewModel: 삭제 API 호출 TODO 문구 구체화 (친구 삭제 context 추가)
@UiHyeon-Kim UiHyeon-Kim merged commit 3bf0d6a into develop Mar 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

병합 대기 병합이 가능한 상태

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants