Conversation
Walkthrough사용자 차단(조회/차단/차단해제) API·DTO·도메인 모델·리포지토리 구현과 디자인시스템(버튼/대화상자/아이콘) 변경을 추가하고, 홈·알림·마이페이지 UI와 뷰모델에 삭제·차단 확인 다이얼로그 및 차단된 계정 화면·네비게이션을 도입했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User as 사용자
participant Screen as 화면 (Home/Notification/BlockedAccounts)
participant ViewModel as ViewModel
participant Repo as UserRepository
participant API as UserApiService
User->>Screen: 차단 버튼 클릭 (onBlockClick)
Screen->>ViewModel: ShowBlockDialog(feedId)
ViewModel->>Screen: uiState 업데이트 (blockingUserId, blockingNickname, showBlockDialog=true)
User->>Screen: 차단 확인 클릭
Screen->>ViewModel: OnBlockConfirmed
ViewModel->>Repo: blockUser(userId)
Repo->>API: POST /api/v1/users/blocks/{userId}
API-->>Repo: BaseResponse<Unit>
Repo-->>ViewModel: 성공 응답
ViewModel->>ViewModel: uiState 업데이트 (showBlockDialog=false, 피드 필터링)
ViewModel->>Screen: 사이드이펙트: ShowSnackbar / 네비게이션(옵션)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (5)
core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/icon/BuyOrNotImgs.kt (1)
37-37:BuyOrNotImgs설명과 실제 리소스 범위를 맞춰두는 것을 권장합니다.
NoBlockedUser는 벡터 drawable인데, 현재 KDoc/타입 설명은 PNG/JPG 비트맵 중심으로 한정되어 있어 이후 사용 시 혼동 여지가 있습니다. 문구를Drawable전반(비트맵+벡터 일러스트)으로 완화해두면 유지보수성이 좋아집니다.문서 정합성 개선 예시
/** - * BuyOrNot 프로젝트 전체에서 사용하는 비트맵 이미지 모음 + * BuyOrNot 프로젝트 전체에서 사용하는 이미지 리소스 모음 * - * PNG, JPG 등의 비트맵 이미지 리소스를 통합 관리합니다. - * 벡터 아이콘(SVG)은 [BuyOrNotIcons]를 사용하세요. + * Drawable 기반 이미지 리소스(비트맵/벡터 일러스트)를 통합 관리합니다. + * 작은 액션성 아이콘은 [BuyOrNotIcons]를 사용하세요. * * 사용 예시: @@ /** - * Drawable 비트맵 리소스를 래핑하는 타입 + * Drawable 이미지 리소스를 래핑하는 타입 * - * PNG, JPG 등의 비트맵 이미지를 타입 안전하게 관리하기 위해 사용합니다. + * 비트맵/벡터 일러스트 이미지를 타입 안전하게 관리하기 위해 사용합니다. * painterResource()와 함께 사용하여 Compose에서 이미지를 표시합니다. * * `@property` resId drawable 리소스 ID */🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/icon/BuyOrNotImgs.kt` at line 37, The KDoc/type description for BuyOrNotImgs is too specific to bitmap formats while NoBlockedUser uses a vector drawable; update the documentation and any type comments for BuyOrNotImgs (and the ImgsResource usage) to refer to generic "Drawable" or "image drawable (bitmap or vector)" instead of "PNG/JPG/bitmap" so the resource range matches NoBlockedUser; locate symbols BuyOrNotImgs, NoBlockedUser, and ImgsResource and revise their KDoc/inline comments accordingly to avoid format-specific wording.feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsContract.kt (1)
5-9: ViewModel 계약이 UI 타입에 직접 의존하고 있습니다.
BlockedAccountsUiState가feature.mypage.ui.BlockedUserItem을 참조하면 계층 경계가 약해집니다. 프레젠테이션 모델을ui패키지 밖(예: viewmodel/model)으로 분리해 계약 레이어 독립성을 유지하는 편이 좋습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsContract.kt` around lines 5 - 9, BlockedAccountsUiState currently depends on the UI type BlockedUserItem which breaks layer boundaries; change BlockedAccountsUiState to reference a presentation model (e.g., BlockedUserModel or BlockedUserViewModel) defined outside the ui package (viewmodel/model package) and stop importing com.sseotdabwa.buyornot.feature.mypage.ui.BlockedUserItem; add a mapper in the ViewModel (or a dedicated mapper class) to convert domain entities to the new BlockedUserModel and have the UI layer convert BlockedUserModel → BlockedUserItem at the composition boundary.feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsViewModel.kt (2)
65-68: 로딩 실패 시 에러 상태 표시 고려
loadBlockedUsers실패 시isLoading은 false로 설정되지만 사용자에게 에러 상태가 표시되지 않습니다. 빈 목록과 실제 에러를 구분할 수 없어 재시도 기회를 제공하기 어렵습니다.♻️ 에러 상태 추가 제안
BlockedAccountsUiState에isError: Boolean필드를 추가하고, UI에서 에러 상태일 때 재시도 버튼을 표시하는 방안을 고려해보세요.}.onFailure { throwable -> - updateState { it.copy(isLoading = false) } + updateState { it.copy(isLoading = false, isError = true) } Log.w(TAG, throwable.toString()) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsViewModel.kt` around lines 65 - 68, Add an error flag to the UI state and set/reset it in the loader: add an isError:Boolean (or isError:Boolean = false) field to BlockedAccountsUiState, then in loadBlockedUsers set isLoading=true and isError=false at start, set isError=false on successful completion, and set isError=true (and isLoading=false) in the onFailure block where updateState and Log.w(TAG, ...) are called; use the existing updateState, loadBlockedUsers, and BlockedAccountsUiState symbols so the UI can show a retry button when isError is true.
40-42: 실패 시 사용자 피드백 누락
unblockUser실패 시 로그만 남기고 사용자에게 알림이 없습니다. 네트워크 오류나 서버 에러 발생 시 사용자가 차단 해제 실패를 인지하지 못할 수 있습니다.♻️ 에러 시 스낵바 표시 제안
}.onFailure { throwable -> Log.w(TAG, throwable.toString()) + sendSideEffect(BlockedAccountsSideEffect.ShowSnackbar("차단 해제에 실패했어요. 다시 시도해주세요.")) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsViewModel.kt` around lines 40 - 42, unblockUser 호출의 onFailure 블록에서 단순히 Log.w(TAG, ...)만 남기고 있어 사용자가 실패를 인지하지 못합니다; BlockedAccountsViewModel의 unblockUser 실패 처리 부분(onFailure { Log.w(TAG, ...) })에 사용자 피드백을 내보내도록 수정하세요 — 예를 들어 ViewModel에 오류 이벤트용 SingleLiveEvent/MutableSharedFlow/MutableStateFlow(ex: unblockErrorEvent)를 추가하고 onFailure에서 해당 이벤트에 오류 메시지(또는 리소스 ID)를 emit/ postValue 함으로써 Fragment/Activity가 스낵바/토스트를 표시하도록 하세요. Ensure you reference unblockUser and the onFailure lambda in BlockedAccountsViewModel when implementing the event emission.feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/BlockedAccountsScreen.kt (1)
156-172: AsyncImage placeholder 처리 개선 제안이미지 로딩 중이나 실패 시 기본 placeholder를 설정하면 더 나은 UX를 제공할 수 있습니다.
♻️ Placeholder 추가 제안
AsyncImage( modifier = Modifier .background( color = BuyOrNotTheme.colors.gray500, shape = CircleShape, ).size(42.dp) .clip(CircleShape), model = ImageRequest .Builder(LocalContext.current) .data(profileImageUrl) .crossfade(true) .build(), contentDescription = "UserProfileImage", contentScale = ContentScale.Crop, + placeholder = painterResource(R.drawable.ic_profile_placeholder), + error = painterResource(R.drawable.ic_profile_placeholder), )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/BlockedAccountsScreen.kt` around lines 156 - 172, The AsyncImage currently has no placeholder or error fallback, so add placeholder and error painters to the AsyncImage call (keep using AsyncImage and ImageRequest.Builder with profileImageUrl) to display a default drawable while loading or on failure; e.g., provide painterResource(...) or rememberDrawablePainter(...) for both the placeholder and error parameters and keep the existing modifier (background, size, clip) and contentScale to preserve current styling. Ensure the drawable resource (e.g., ic_profile_placeholder) exists and import painterResource/rememberDrawablePainter and Coil's AsyncImage parameters so loading and failure states show the fallback image.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/FeedCard.kt`:
- Line 78: The FeedCard currently always exposes a "block" action via the
onBlockClick callback even for guest/non-owner flows; add an explicit opt-in API
so callers can hide the block menu when not available (e.g., change
onBlockClick: () -> Unit = {} to either onBlockClick: (() -> Unit)? = null and
only render the block item when non-null, or add a parameter like showBlockMenu:
Boolean = true and gate rendering on that flag) and update all call sites to
pass null/false for guest flows; adjust rendering logic in FeedCard (and the
identical code region referenced around lines 160-174) to check the new nullable
callback or flag before showing the "차단하기" menu.
In
`@feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeContract.kt`:
- Around line 43-45: Remove the unsafe default for authorUserId by changing the
property declaration (authorUserId) to be required (no "= 0L" default) so
FeedItem construction fails at compile time if the real server ID isn’t
provided; update all call sites that construct this type (notably
Feed.toFeedItem()) to pass the correct authorUserId and fix any tests or usages
that relied on the default to avoid accidental calls like blockUser(0).
In
`@feature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailViewModel.kt`:
- Around line 120-129: In handleBlockConfirmed, ensure the dialog is closed
unconditionally before any early returns: call updateState to set
showBlockDialog = false at the start of the function (or immediately before any
null-checks) so the dialog is dismissed even if uiState.value.feed or
feed.author.userId is null; then proceed to read
uiState.value.feed?.author?.userId and nickname and return early if userId is
null, then continue with the block logic (use the existing uiState, updateState
and showBlockDialog identifiers to locate and modify the code).
---
Nitpick comments:
In
`@core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/icon/BuyOrNotImgs.kt`:
- Line 37: The KDoc/type description for BuyOrNotImgs is too specific to bitmap
formats while NoBlockedUser uses a vector drawable; update the documentation and
any type comments for BuyOrNotImgs (and the ImgsResource usage) to refer to
generic "Drawable" or "image drawable (bitmap or vector)" instead of
"PNG/JPG/bitmap" so the resource range matches NoBlockedUser; locate symbols
BuyOrNotImgs, NoBlockedUser, and ImgsResource and revise their KDoc/inline
comments accordingly to avoid format-specific wording.
In
`@feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/BlockedAccountsScreen.kt`:
- Around line 156-172: The AsyncImage currently has no placeholder or error
fallback, so add placeholder and error painters to the AsyncImage call (keep
using AsyncImage and ImageRequest.Builder with profileImageUrl) to display a
default drawable while loading or on failure; e.g., provide painterResource(...)
or rememberDrawablePainter(...) for both the placeholder and error parameters
and keep the existing modifier (background, size, clip) and contentScale to
preserve current styling. Ensure the drawable resource (e.g.,
ic_profile_placeholder) exists and import
painterResource/rememberDrawablePainter and Coil's AsyncImage parameters so
loading and failure states show the fallback image.
In
`@feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsContract.kt`:
- Around line 5-9: BlockedAccountsUiState currently depends on the UI type
BlockedUserItem which breaks layer boundaries; change BlockedAccountsUiState to
reference a presentation model (e.g., BlockedUserModel or BlockedUserViewModel)
defined outside the ui package (viewmodel/model package) and stop importing
com.sseotdabwa.buyornot.feature.mypage.ui.BlockedUserItem; add a mapper in the
ViewModel (or a dedicated mapper class) to convert domain entities to the new
BlockedUserModel and have the UI layer convert BlockedUserModel →
BlockedUserItem at the composition boundary.
In
`@feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsViewModel.kt`:
- Around line 65-68: Add an error flag to the UI state and set/reset it in the
loader: add an isError:Boolean (or isError:Boolean = false) field to
BlockedAccountsUiState, then in loadBlockedUsers set isLoading=true and
isError=false at start, set isError=false on successful completion, and set
isError=true (and isLoading=false) in the onFailure block where updateState and
Log.w(TAG, ...) are called; use the existing updateState, loadBlockedUsers, and
BlockedAccountsUiState symbols so the UI can show a retry button when isError is
true.
- Around line 40-42: unblockUser 호출의 onFailure 블록에서 단순히 Log.w(TAG, ...)만 남기고 있어
사용자가 실패를 인지하지 못합니다; BlockedAccountsViewModel의 unblockUser 실패 처리 부분(onFailure {
Log.w(TAG, ...) })에 사용자 피드백을 내보내도록 수정하세요 — 예를 들어 ViewModel에 오류 이벤트용
SingleLiveEvent/MutableSharedFlow/MutableStateFlow(ex: unblockErrorEvent)를 추가하고
onFailure에서 해당 이벤트에 오류 메시지(또는 리소스 ID)를 emit/ postValue 함으로써 Fragment/Activity가
스낵바/토스트를 표시하도록 하세요. Ensure you reference unblockUser and the onFailure lambda in
BlockedAccountsViewModel when implementing the event emission.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 116c0c48-9c51-40f2-bf17-66156742cff6
📒 Files selected for processing (21)
core/data/src/main/java/com/sseotdabwa/buyornot/core/data/repository/UserRepositoryImpl.ktcore/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/AlertDialog.ktcore/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/Button.ktcore/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/FeedCard.ktcore/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/icon/BuyOrNotImgs.ktcore/designsystem/src/main/res/drawable/img_blocked_user_empty.xmlcore/network/src/main/java/com/sseotdabwa/buyornot/core/network/api/UserApiService.ktcore/network/src/main/java/com/sseotdabwa/buyornot/core/network/dto/response/BlockedUser.ktdomain/src/main/java/com/sseotdabwa/buyornot/domain/model/BlockedUser.ktdomain/src/main/java/com/sseotdabwa/buyornot/domain/repository/UserRepository.ktfeature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeContract.ktfeature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeScreen.ktfeature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeViewModel.ktfeature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/navigation/MyPageNavigation.ktfeature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/BlockedAccountsScreen.ktfeature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/MyPageScreen.ktfeature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsContract.ktfeature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsViewModel.ktfeature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailContract.ktfeature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailScreen.ktfeature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailViewModel.kt
.../designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/FeedCard.kt
Show resolved
Hide resolved
feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeContract.kt
Outdated
Show resolved
Hide resolved
...src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailViewModel.kt
Outdated
Show resolved
Hide resolved
5b6e02c to
b381046
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
feature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailViewModel.kt (1)
120-129:⚠️ Potential issue | 🟡 Minor차단 확인 시 조기 반환 경로에서도 다이얼로그를 먼저 닫아야 합니다.
Line 124에서
?: return이 먼저 실행되면 Line 129가 실행되지 않아 다이얼로그가 남을 수 있습니다.showBlockDialog = false를 함수 시작 시점에 먼저 적용하세요.🔧 제안 수정안
private fun handleBlockConfirmed() { - val userId = - uiState.value.feed - ?.author - ?.userId ?: return - val nickname = - uiState.value.feed - ?.author - ?.nickname updateState { it.copy(showBlockDialog = false) } + val feed = uiState.value.feed ?: return + val userId = feed.author.userId + val nickname = feed.author.nickname viewModelScope.launch { runCatchingCancellable { userRepository.blockUser(userId)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailViewModel.kt` around lines 120 - 129, In handleBlockConfirmed, set showBlockDialog = false at the start so the dialog is closed even when the early return triggers; move the updateState { it.copy(showBlockDialog = false) } call to the top of the function (before reading uiState.value.feed and before the ?: return) so the dialog is always dismissed, then proceed to read userId/nickname and return early as needed.
🧹 Nitpick comments (1)
feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeViewModel.kt (1)
329-336: ViewModel 레벨에서 자기 자신 차단 방어를 추가해두면 더 안전합니다.현재는 업스트림 UI가 자기 글에 차단 메뉴를 노출하지 않는다는 전제를 두고 있습니다. 그래도
Line 330이후에feed.isOwner방어를 넣으면 잘못된 인텐트 유입 시 불필요한 차단 요청을 예방할 수 있습니다.예시 수정안
private fun handleShowBlockDialog(feedId: String) { val feed = uiState.value.feeds.find { it.id == feedId } ?: return + if (feed.isOwner) return updateState { it.copy( showBlockDialog = true, blockingNickname = feed.nickname, blockingUserId = feed.authorUserId, ) } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeViewModel.kt` around lines 329 - 336, Add a defensive check in handleShowBlockDialog: after finding the feed (in function handleShowBlockDialog) verify feed.isOwner is false and return early if true, so you don't set showBlockDialog / blockingNickname / blockingUserId for the current user's own post; update the method to only call updateState(...) when feed != null and feed.isOwner == false, using the existing feed variable and the state fields (showBlockDialog, blockingNickname, blockingUserId) to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeViewModel.kt`:
- Around line 340-351: handleBlockConfirmed uses nullable
uiState.value.blockingNickname directly in the snackbar message, which can yield
"null님이 차단되었어요."; update handleBlockConfirmed to provide a safe fallback for
blockingNickname (e.g., use the elvis operator or conditional) when constructing
the HomeSideEffect.ShowSnackbar message so it never includes "null" (refer to
handleBlockConfirmed, uiState, blockingNickname, and
HomeSideEffect.ShowSnackbar).
---
Duplicate comments:
In
`@feature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailViewModel.kt`:
- Around line 120-129: In handleBlockConfirmed, set showBlockDialog = false at
the start so the dialog is closed even when the early return triggers; move the
updateState { it.copy(showBlockDialog = false) } call to the top of the function
(before reading uiState.value.feed and before the ?: return) so the dialog is
always dismissed, then proceed to read userId/nickname and return early as
needed.
---
Nitpick comments:
In
`@feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeViewModel.kt`:
- Around line 329-336: Add a defensive check in handleShowBlockDialog: after
finding the feed (in function handleShowBlockDialog) verify feed.isOwner is
false and return early if true, so you don't set showBlockDialog /
blockingNickname / blockingUserId for the current user's own post; update the
method to only call updateState(...) when feed != null and feed.isOwner ==
false, using the existing feed variable and the state fields (showBlockDialog,
blockingNickname, blockingUserId) to locate the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 4918348b-bf41-45b5-805f-4bff1c49c0a4
📒 Files selected for processing (3)
app/src/main/java/com/sseotdabwa/buyornot/navigation/BuyOrNotNavHost.ktfeature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeViewModel.ktfeature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailViewModel.kt
feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeViewModel.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsViewModel.kt (1)
31-75: block/unblock 로직 중복을 공통 함수로 줄이면 유지보수가 쉬워집니다.API 호출, 성공 시 상태 업데이트, 실패 로깅/피드백 흐름이 거의 동일해서 공통 헬퍼로 추출하면 분기 추가 시 누락 가능성을 줄일 수 있습니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsViewModel.kt` around lines 31 - 75, blockUser and unblockUser duplicate the same pattern (runCatchingCancellable -> repository call -> updateState mapping -> sendSideEffect/logging); extract a shared private helper (e.g., changeBlockStatus or performBlockAction) that accepts the userId, nickname, a suspend lambda for the API call (call userRepository.blockUser or userRepository.unblockUser), the desired isBlocked boolean and the success message; have unblockUser and blockUser call that helper, keep runCatchingCancellable, updateState mapping logic (iterating blockedUsers and comparing user.userId) and Log.w/side effect handling inside the helper to consolidate behavior.feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeViewModel.kt (1)
354-354: 차단 성공 후 피드 필터링 순서 확인이 필요합니다.스낵바 표시 후 피드 목록을 필터링하고 있습니다. 스낵바 전송(
sendSideEffect)은 suspend가 아니므로 순서에 문제는 없지만, 사용자 경험상 피드가 먼저 사라지고 스낵바가 표시되는 것이 더 자연스러울 수 있습니다.♻️ 순서 변경 제안 (선택 사항)
}.onSuccess { + updateState { it.copy(feeds = it.feeds.filter { feed -> feed.authorUserId != userId }) } sendSideEffect( HomeSideEffect.ShowSnackbar( message = "${nickname}님이 차단되었어요.", icon = null, ), ) - updateState { it.copy(feeds = it.feeds.filter { feed -> feed.authorUserId != userId }) } }.onFailure { e ->🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeViewModel.kt` at line 354, The current flow calls sendSideEffect (snackbar) before updating feeds; change the order so updateState { it.copy(feeds = it.feeds.filter { feed -> feed.authorUserId != userId }) } runs first to remove the blocked user's posts, then call sendSideEffect to show the snackbar; locate the feed-removal logic and the sendSideEffect invocation in HomeViewModel (methods handling the block action) and swap their order so the UI updates before the snackbar is shown.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsViewModel.kt`:
- Around line 48-50: The current onFailure blocks only call Log.w(TAG,
throwable.toString()) and do not surface errors to the UI; update each onFailure
lambda in BlockedAccountsViewModel (the failure handlers in the functions that
perform block/unblock/list operations) to emit a user-facing side-effect (e.g.,
send or post a SideEffect such as ShowSnackbar or ShowError) carrying a friendly
message (use throwable.message ?: a default string) instead of only logging;
apply this change to the three onFailure occurrences shown (the onFailure in the
list fetch, the block action, and the unblock action) so the UI can display a
snackbar/toast when operations fail.
- Around line 39-45: The current updateState closure only flips isBlocked to
false but keeps the user in the blockedUsers list, so unblocking the last user
never yields an empty "blocked accounts" list; change the update to remove the
unblocked user from blockedUsers instead of mapping it—inside the
BlockedAccountsViewModel updateState block that currently references
blockedUsers and user.userId/isBlocked, replace the map logic with a filter
(e.g., filterNot by userId) or otherwise drop the matching user when an unblock
succeeds so the list reflects only remaining blocked accounts.
---
Nitpick comments:
In
`@feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeViewModel.kt`:
- Line 354: The current flow calls sendSideEffect (snackbar) before updating
feeds; change the order so updateState { it.copy(feeds = it.feeds.filter { feed
-> feed.authorUserId != userId }) } runs first to remove the blocked user's
posts, then call sendSideEffect to show the snackbar; locate the feed-removal
logic and the sendSideEffect invocation in HomeViewModel (methods handling the
block action) and swap their order so the UI updates before the snackbar is
shown.
In
`@feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsViewModel.kt`:
- Around line 31-75: blockUser and unblockUser duplicate the same pattern
(runCatchingCancellable -> repository call -> updateState mapping ->
sendSideEffect/logging); extract a shared private helper (e.g.,
changeBlockStatus or performBlockAction) that accepts the userId, nickname, a
suspend lambda for the API call (call userRepository.blockUser or
userRepository.unblockUser), the desired isBlocked boolean and the success
message; have unblockUser and blockUser call that helper, keep
runCatchingCancellable, updateState mapping logic (iterating blockedUsers and
comparing user.userId) and Log.w/side effect handling inside the helper to
consolidate behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: be8ceac5-2c28-4986-ae7f-8ec937f91117
📒 Files selected for processing (10)
core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/FeedCard.ktfeature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeContract.ktfeature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeScreen.ktfeature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeViewModel.ktfeature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/BlockedAccountsScreen.ktfeature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsContract.ktfeature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsViewModel.ktfeature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailContract.ktfeature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailScreen.ktfeature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailViewModel.kt
🚧 Files skipped from review as they are similar to previous changes (5)
- feature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailScreen.kt
- feature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailViewModel.kt
- feature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailContract.kt
- feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/BlockedAccountsScreen.kt
- core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/FeedCard.kt
...e/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsViewModel.kt
Show resolved
Hide resolved
...e/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsViewModel.kt
Show resolved
Hide resolved
|
@coderabbitai 승인하라고 이씨 |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/BlockedAccountsScreen.kt (1)
156-172: AsyncImage에 placeholder/error 처리 추가 권장현재
.background()로 기본 회색 배경은 있지만, 이미지 로딩 실패 시 사용자에게 명확한 피드백이 없습니다. placeholder와 error 파라미터 추가를 고려해 주세요.또한
contentDescription이 영문 하드코딩되어 있어 접근성/다국어 지원에 영향을 줄 수 있습니다.♻️ 개선 제안
AsyncImage( modifier = Modifier .background( color = BuyOrNotTheme.colors.gray500, shape = CircleShape, ).size(42.dp) .clip(CircleShape), model = ImageRequest .Builder(LocalContext.current) .data(profileImageUrl) .crossfade(true) .build(), - contentDescription = "UserProfileImage", + contentDescription = "프로필 이미지", + placeholder = painterResource(R.drawable.placeholder_profile), + error = painterResource(R.drawable.placeholder_profile), contentScale = ContentScale.Crop, )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/BlockedAccountsScreen.kt` around lines 156 - 172, The AsyncImage usage lacks placeholder/error UI and has a hardcoded English contentDescription; update the AsyncImage call (the AsyncImage composable that builds an ImageRequest from profileImageUrl) to pass placeholder and error painters (e.g., painterResource or rememberVectorPainter of a local drawable like ic_profile_placeholder) and consider a fallback when profileImageUrl is null, and replace the hardcoded "UserProfileImage" contentDescription with a localized string via stringResource (e.g., R.string.user_profile_image) to improve accessibility and i18n.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/BlockedAccountsScreen.kt`:
- Around line 156-172: The AsyncImage usage lacks placeholder/error UI and has a
hardcoded English contentDescription; update the AsyncImage call (the AsyncImage
composable that builds an ImageRequest from profileImageUrl) to pass placeholder
and error painters (e.g., painterResource or rememberVectorPainter of a local
drawable like ic_profile_placeholder) and consider a fallback when
profileImageUrl is null, and replace the hardcoded "UserProfileImage"
contentDescription with a localized string via stringResource (e.g.,
R.string.user_profile_image) to improve accessibility and i18n.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 3a11bfb5-ca36-44f8-bfd7-e1c3f2d5a07e
📒 Files selected for processing (1)
feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/BlockedAccountsScreen.kt
|
✅ Actions performedComments resolved and changes approved. |
🛠 Related issue
closed #68
어떤 변경사항이 있었나요?
✅ CheckPoint
PR이 다음 요구 사항을 충족하는지 확인하세요.
✏️ Work Description
BlockedAccountsScreen을 새로 구현했습니다. 차단된 유저가 없을 때의 빈 화면도 함께 처리했습니다.UserApiService에 차단/차단 목록 조회/차단 해제 API 엔드포인트를 추가하고,UserRepository및UserRepositoryImpl에 관련 메서드를 구현했습니다.😅 Uncompleted Tasks
📢 To Reviewers
BlockedAccountsViewModel은 MVI 패턴(BlockedAccountsContract)을 따르며, 목록 조회 및 차단 해제 이벤트를 각각 분리하여 처리합니다.📃 RCA 룰
Summary by CodeRabbit
새로운 기능
UI/컴포넌트
스타일/리소스