Skip to content

✨ Feature - 차단 기능 추가, 삭제 다이얼로그 추가#69

Merged
DongChyeon merged 21 commits intodevelopfrom
feature/#68/block-user
Mar 19, 2026
Merged

✨ Feature - 차단 기능 추가, 삭제 다이얼로그 추가#69
DongChyeon merged 21 commits intodevelopfrom
feature/#68/block-user

Conversation

@DongChyeon
Copy link
Member

@DongChyeon DongChyeon commented Mar 18, 2026

🛠 Related issue

closed #68

어떤 변경사항이 있었나요?

  • 🐞 BugFix Something isn't working
  • 🎨 Design Markup & styling
  • 📃 Docs Documentation writing and editing (README.md, etc.)
  • ✨ Feature Feature
  • 🔨 Refactor Code refactoring
  • ⚙️ Setting Development environment setup
  • ✅ Test Test related (Junit, etc.)

✅ CheckPoint

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • PR 컨벤션에 맞게 작성했습니다. (필수)
  • merge할 브랜치의 위치를 확인해 주세요(main❌/develop⭕) (필수)
  • Approve된 PR은 assigner가 머지하고, 수정 요청이 온 경우 수정 후 다시 push를 합니다. (필수)
  • BugFix의 경우, 버그의 원인을 파악하였습니다. (선택)

✏️ Work Description

삭제하기 피드 메뉴 팝업 차단하기
  • 차단하기 기능 추가 (홈 & 알림 상세): 본인 피드가 아닐 경우 FeedCard 팝업에 "차단하기" 메뉴를 노출하고, 홈 화면 및 알림 상세 화면에서 차단 API를 연동했습니다.
  • 차단된 계정 목록 화면 구현: 마이페이지에 "차단된 계정" 메뉴를 추가하고, 차단된 유저 목록을 조회하는 BlockedAccountsScreen을 새로 구현했습니다. 차단된 유저가 없을 때의 빈 화면도 함께 처리했습니다.
  • 차단 해제 기능 구현: 차단된 유저 목록 화면에서 개별 유저를 차단 해제할 수 있는 버튼을 추가하고, 차단 해제 API를 연동했습니다.
  • 차단 관련 네트워크 계층 구성: UserApiService에 차단/차단 목록 조회/차단 해제 API 엔드포인트를 추가하고, UserRepositoryUserRepositoryImpl에 관련 메서드를 구현했습니다.
  • 삭제하기 Dialog 구현: 피드를 삭제할 때 바로 삭제가 되는 것이 아닌 Dialog를 노출함으로써 유저로부터 한번 더 확인합니다.

😅 Uncompleted Tasks

  • N/A

📢 To Reviewers

  • BlockedAccountsViewModel은 MVI 패턴(BlockedAccountsContract)을 따르며, 목록 조회 및 차단 해제 이벤트를 각각 분리하여 처리합니다.
  • 차단 해제 성공 시 로컬 상태에서 해당 유저를 즉시 제거하여 별도의 목록 재조회 없이 UI가 업데이트됩니다.

📃 RCA 룰

  • R: 꼭 반영해 주세요. 적극적으로 고려해 주세요. (Request changes)
  • C: 웬만하면 반영해 주세요. (Comment)
  • A: 반영해도 좋고 넘어가도 좋습니다. 그냥 사소한 의견입니다. (Approve)

Summary by CodeRabbit

  • 새로운 기능

    • 피드에서 사용자 차단 및 차단해제 기능 추가(차단 시 해당 작성자 게시물 숨김)
    • 마이페이지에 “차단된 계정” 관리 화면 추가(목록 조회·차단/해제)
    • 피드·알림 상세에서 삭제/차단 확인 대화상자 추가(확인/취소 흐름)
    • 더보기 메뉴가 게스트에겐 비노출 처리
  • UI/컴포넌트

    • 확인 버튼에 파괴적 스타일(붉은색) 적용 옵션 및 버튼 색상 구성 가능
    • 액션 팝업이 다중 항목 메뉴로 확장
  • 스타일/리소스

    • 차단 빈 상태 일러스트 아이콘 추가

@DongChyeon DongChyeon requested a review from Imagine-Choi March 18, 2026 14:57
@DongChyeon DongChyeon self-assigned this Mar 18, 2026
@DongChyeon DongChyeon added ✨ FEAT 기능 개발 (애매하면 기능 개발로 두도록 하자) 💪 동현동현동현 labels Mar 18, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

Walkthrough

사용자 차단(조회/차단/차단해제) API·DTO·도메인 모델·리포지토리 구현과 디자인시스템(버튼/대화상자/아이콘) 변경을 추가하고, 홈·알림·마이페이지 UI와 뷰모델에 삭제·차단 확인 다이얼로그 및 차단된 계정 화면·네비게이션을 도입했습니다.

Changes

Cohort / File(s) Summary
네트워크 - DTO 및 API
core/network/.../dto/response/BlockedUser.kt, core/network/.../api/UserApiService.kt
차단 사용자 응답 DTO(BlockedUser) 추가 및 getBlockedUsers(), blockUser(userId), unblockUser(userId) API 엔드포인트 추가
도메인 - 모델 및 리포지토리 인터페이스
domain/.../model/BlockedUser.kt, domain/.../repository/UserRepository.kt
도메인 BlockedUser 모델 추가 및 리포지토리 인터페이스에 차단 관련 메서드 시그니처(getBlockedUsers, blockUser, unblockUser) 추가
데이터 계층 - 저장소 구현
core/data/.../repository/UserRepositoryImpl.kt
리포지토리 구현에 getBlockedUsers(), blockUser(), unblockUser() 추가(API 호출 및 응답→도메인 매핑 포함)
디자인시스템 - 컴포넌트·리소스
core/designsystem/.../Button.kt, core/designsystem/.../AlertDialog.kt, core/designsystem/.../FeedCard.kt, core/designsystem/.../icon/BuyOrNotImgs.kt, core/designsystem/.../res/drawable/img_blocked_user_empty.xml
파괴적 버튼 색상(default) 및 AlertDialog confirm 색상 파라미터 추가, FeedCard에 블록 액션·메뉴 구조(복수 아이템)로 변경, 차단 빈 상태 아이콘 리소스 추가
홈(Feature) - 계약·화면·뷰모델
feature/home/.../HomeContract.kt, feature/home/.../HomeScreen.kt, feature/home/.../HomeViewModel.kt
FeedItem에 authorUserId 추가; 삭제·차단 대화상자 인텐트·상태 추가; 화면에서 다이얼로그 표시 및 블록 흐름(레포 호출 → 피드 필터링 · 스낵바) 구현
마이페이지(Feature) - 네비게이션·화면·뷰모델·계약
feature/mypage/.../navigation/MyPageNavigation.kt, feature/mypage/.../ui/BlockedAccountsScreen.kt, feature/mypage/.../ui/MyPageScreen.kt, feature/mypage/.../viewmodel/BlockedAccountsContract.kt, feature/mypage/.../viewmodel/BlockedAccountsViewModel.kt
BlockedAccounts 라우트/네비게이션 헬퍼 추가; 차단 계정 목록 화면·계약·뷰모델 구현 및 MyPage에 항목 추가
알림(Feature) - 계약·화면·뷰모델
feature/notification/.../NotificationDetailContract.kt, feature/notification/.../NotificationDetailScreen.kt, feature/notification/.../NotificationDetailViewModel.kt
Notification 상세에 삭제·차단 다이얼로그 인텐트/상태 추가; 화면·뷰모델에 블록 확인 처리 흐름 추가
앱 네비게이션 변경
app/src/main/java/.../BuyOrNotNavHost.kt
Upload→Home 전환 시 popUpTo 타겟을 UPLOAD_ROUTE로 변경해 백스택 정리 동작 조정

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 / 네비게이션(옵션)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #31: UserRepository 및 UserApiService 계층을 수정한 PR — 리포지토리/API 확장(차단 관련 엔드포인트/메서드)과 직접 연관됨.
  • PR #57: UserRepositoryImpl에 새로운 suspend 메서드를 추가한 PR — 같은 클래스에 대한 변경(메서드 추가)으로 충돌 가능성 있음.
  • PR #24: MyPage 네비게이션을 다룬 PR — MyPage 네비게이션/라우트 변경과 직접 연관됨.

Suggested reviewers

  • Imagine-Choi
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning BuyOrNotNavHost.kt의 네비게이션 pop-up 설정 변경은 스코프 외의 변경이며, 직접적인 차단/삭제 기능과 관련이 없습니다. BuyOrNotNavHost.kt의 네비게이션 변경 내용을 설명하거나, 이것이 차단/삭제 기능 구현에 필요한 변경인 경우 PR 설명에 명시하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 10.20% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 주요 변경 사항(차단 기능 추가, 삭제 다이얼로그 추가)을 명확하게 요약하고 있으며, 이는 변경 사항의 핵심을 정확히 반영합니다.
Linked Issues check ✅ Passed PR의 모든 변경 사항이 연결된 이슈 #68의 요구사항을 충족합니다. 차단 기능(blockUser, unblockUser, getBlockedUsers)과 삭제 다이얼로그가 완전히 구현되었습니다.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#68/block-user
📝 Coding Plan
  • Generate coding plan for human review comments

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

@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: 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 타입에 직접 의존하고 있습니다.

BlockedAccountsUiStatefeature.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로 설정되지만 사용자에게 에러 상태가 표시되지 않습니다. 빈 목록과 실제 에러를 구분할 수 없어 재시도 기회를 제공하기 어렵습니다.

♻️ 에러 상태 추가 제안

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

📥 Commits

Reviewing files that changed from the base of the PR and between 53c38e9 and 810e477.

📒 Files selected for processing (21)
  • core/data/src/main/java/com/sseotdabwa/buyornot/core/data/repository/UserRepositoryImpl.kt
  • core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/AlertDialog.kt
  • core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/Button.kt
  • core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/FeedCard.kt
  • core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/icon/BuyOrNotImgs.kt
  • core/designsystem/src/main/res/drawable/img_blocked_user_empty.xml
  • core/network/src/main/java/com/sseotdabwa/buyornot/core/network/api/UserApiService.kt
  • core/network/src/main/java/com/sseotdabwa/buyornot/core/network/dto/response/BlockedUser.kt
  • domain/src/main/java/com/sseotdabwa/buyornot/domain/model/BlockedUser.kt
  • domain/src/main/java/com/sseotdabwa/buyornot/domain/repository/UserRepository.kt
  • feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeContract.kt
  • feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeScreen.kt
  • feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeViewModel.kt
  • feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/navigation/MyPageNavigation.kt
  • feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/BlockedAccountsScreen.kt
  • feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/MyPageScreen.kt
  • feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsContract.kt
  • feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsViewModel.kt
  • feature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailContract.kt
  • 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

@DongChyeon DongChyeon force-pushed the feature/#68/block-user branch from 5b6e02c to b381046 Compare March 18, 2026 15:11
Copy link

@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)
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

📥 Commits

Reviewing files that changed from the base of the PR and between 810e477 and b381046.

📒 Files selected for processing (3)
  • app/src/main/java/com/sseotdabwa/buyornot/navigation/BuyOrNotNavHost.kt
  • feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeViewModel.kt
  • feature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailViewModel.kt

@DongChyeon DongChyeon changed the title ✨ Feature - 차단 기능 추가, 삭제 다이얼로그 추 ✨ Feature - 차단 기능 추가, 삭제 다이얼로그 추가 Mar 19, 2026
Copy link

@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

🧹 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

📥 Commits

Reviewing files that changed from the base of the PR and between b381046 and c32e739.

📒 Files selected for processing (10)
  • core/designsystem/src/main/java/com/sseotdabwa/buyornot/core/designsystem/components/FeedCard.kt
  • feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeContract.kt
  • feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeScreen.kt
  • feature/home/src/main/java/com/sseotdabwa/buyornot/feature/home/ui/HomeViewModel.kt
  • feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/BlockedAccountsScreen.kt
  • feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsContract.kt
  • feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/viewmodel/BlockedAccountsViewModel.kt
  • feature/notification/src/main/java/com/sseotdabwa/buyornot/feature/notification/ui/NotificationDetailContract.kt
  • 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
🚧 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

@DongChyeon
Copy link
Member Author

@coderabbitai 승인하라고 이씨

Copy link

@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 (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

📥 Commits

Reviewing files that changed from the base of the PR and between c32e739 and a0081b6.

📒 Files selected for processing (1)
  • feature/mypage/src/main/java/com/sseotdabwa/buyornot/feature/mypage/ui/BlockedAccountsScreen.kt

@DongChyeon DongChyeon merged commit a6d7e9b into develop Mar 19, 2026
2 checks passed
@coderabbitai
Copy link

coderabbitai bot commented Mar 19, 2026

@DongChyeon 넵넵! 바로 승인해드리겠습니다! 🐇✨

✅ Actions performed

Comments resolved and changes approved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ FEAT 기능 개발 (애매하면 기능 개발로 두도록 하자) 💪 동현동현동현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Feature - 차단 기능 추가, 삭제 다이얼로그 추가

1 participant