Skip to content

Conversation

@onlyone130
Copy link
Contributor

@onlyone130 onlyone130 commented Aug 13, 2025

체크리스트

  • 빌드가 되는 것을 확인 하셨나요??
  • 리뷰어 할당과 본인 할당 잊지마세요
  • 적절한 태그를 부여 해주세요

📝작업 내용

이번 PR에서 작업한 내용을 간략히 설명해주세요

프로필 화면에서 차단 리스트 이동 구현

스크린샷

작업한 화면기능의 스크린샷을 첨부해 주세요

스크린샷 2025-08-13 오후 12 54 20 스크린샷 2025-08-13 오후 12 55 06

💬리뷰 요구사항(선택)

리뷰어가 특별히 봐주었으면 하는 부분이나 함께 고민해봐야할 부분이 있다면 작성해주세요

Summary by CodeRabbit

  • 신기능
    • 프로필에서 닉네임 변경 팝업 추가(중복 확인 지원).
    • 의견 보내기 다이얼로그 추가.
    • 차단 리스트 화면 추가 및 사용자 차단 해제 옵션 제공.
  • UI
    • 프로필 화면 전면 개편: 섹션 재구성, 폰트/간격 조정, 로그아웃 버튼 추가.
    • 입력/버튼 배경, 아이콘(더보기 등) 스타일 리소스 추가.
    • 차단 사용자 아이템 카드 레이아웃 추가.
  • 내비게이션
    • 프로필 → 차단 리스트로 이동 경로 추가.

@onlyone130 onlyone130 requested a review from sungahbak August 13, 2025 03:55
@onlyone130 onlyone130 self-assigned this Aug 13, 2025
@onlyone130 onlyone130 added the 🌟 Feature 기능개발 label Aug 13, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 13, 2025

Walkthrough

  • 프로필 화면 UI 전면 개편 및 레이아웃 재구성(닉네임/인기도/활동/차단 리스트/의견/컬렉션/로고/로그아웃).
  • 닉네임 변경 플로우 도입: ChangeNicknamePopup(DialogFragment) + ChangeNicknameViewModel + NicknameRepository/Service.
  • 닉네임 중복 확인 API 모델/서비스/레포지토리 추가(NicknameService, NicknameRepository/Impl, NicknameResponse/Data).
  • DI 추가/개편: ServiceModule에 Nickname/Join 서비스 제공자 추가, RepositoryModule에 Nickname/Join 레포 제공자 이동/추가, NetworkModule 중첩 모듈 제거 및 제공자 정리.
  • 차단 리스트 화면/어댑터/아이템/메뉴 및 네비게이션 액션 추가.
  • 새로운 드로어블/레이아웃(팝업/다이얼로그/버튼 배경/아이콘) 추가.

Sequence Diagram(s)

sequenceDiagram
actor U as User
participant PF as ProfileFragment
participant CNP as ChangeNicknamePopup
participant VM as ChangeNicknameViewModel
participant NR as NicknameRepository
participant NS as NicknameService
participant API as Server API

U->>PF: 닉네임 영역 클릭
PF->>CNP: 팝업 표시(onCheckNickname 콜백 설정)
CNP->>CNP: 입력 유효성 검사(3~10자)
CNP->>VM: checkNickname(newNickname, callback)
VM->>NR: isNicknameDuplicated(nickname)
NR->>NS: checkNickname(nickname)
NS->>API: GET /auth/check-nickname/{nickname}
API-->>NS: NicknameResponse(success,data.available)
NS-->>NR: available
NR-->>VM: duplicated = !available
VM-->>CNP: 콜백(duplicated)
CNP->>U: 토스트/버튼 상태 업데이트
Loading
sequenceDiagram
actor U as User
participant PF as ProfileFragment
participant NC as NavController
participant BLF as BlockListFragment
participant RCV as RecyclerView
participant BLA as BlockListAdapter
participant PM as PopupMenu

U->>PF: "차단 리스트" 클릭
PF->>NC: navigate(action_profileFragment_to_blockListFragment)
NC->>BLF: BlockListFragment 표시
BLF->>RCV: 어댑터 설정(BLA)
BLF->>BLA: submitList(mock BlockedUser 3건)
U->>BLA: 아이템의 더보기 클릭
BLA->>PM: 메뉴 표시(menu_block_option)
U->>PM: "차단 해제" 선택
PM-->>BLA: onUnblockClick(user)
BLA-->>BLF: 콜백 전달
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/온보딩-프로필신고및차단

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🔭 Outside diff range comments (7)
app/src/main/java/umc/OnAirMate/ui/chat_room/ChatRoomFragment.kt (5)

82-85: YouTubePlayerView는 viewLifecycleOwner에 바인딩하세요 (메모리 누수/크래시 방지)

Fragment의 view가 파괴된 뒤(fragment 재사용 시)에도 Fragment lifecycle에 붙어 있으면 누수나 크래시가 발생할 수 있습니다. View 기반 리소스는 viewLifecycleOwner에 연결하는 것이 안전합니다.

아래처럼 변경을 권장합니다:

-        val youtubePlayer = binding.youtubePlayer
-        lifecycle.addObserver(youtubePlayer)
+        val youtubePlayer = binding.youtubePlayer
+        viewLifecycleOwner.lifecycle.addObserver(youtubePlayer)

30-35: API 33 전후 getParcelable 호환 처리 누락 (하위 OS에서 런타임 크래시 위험)

onCreate는 모든 API에서 호출되므로, API 33 전용 오버로드를 직접 호출하면 하위 버전에서 NoSuchMethod 에러로 크래시됩니다. @RequiresApi로 메서드를 데코레이트하는 것만으로는 런타임 보호가 되지 않습니다.

다음처럼 버전 분기 처리를 적용해 주세요. 또한 @RequiresApi(Build.VERSION_CODES.TIRAMISU)는 제거하는 것이 맞습니다.

-    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        roomData = arguments?.getParcelable("room_data", RoomData::class.java)!!
+        roomData = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            arguments?.getParcelable("room_data", RoomData::class.java)
+        } else {
+            @Suppress("DEPRECATION")
+            arguments?.getParcelable("room_data")
+        }!!
 
     }

추가로, @RequiresApi 제거 후 관련 import가 불필요해질 수 있으니 정리해 주세요.


26-27: Fragment ViewBinding 패턴 수정 필요 — 현재 형태는 메모리 누수 소지

Fragment에서 binding을 lateinit으로 보관하면 onDestroyView 이후에도 View 트리를 붙잡아 누수 위험이 있습니다. 권장 패턴은 nullable backing 필드(_binding) + onDestroyView에서 null 처리입니다.

아래처럼 변경을 권장합니다:

-    lateinit var binding: FragmentChatRoomBinding
+    private var _binding: FragmentChatRoomBinding? = null
+    private val binding get() = _binding!!

onCreateView의 inflate 할당과 onDestroyView의 해제도 함께 반영해 주세요(아래 별도 코멘트 참조).


37-45: inflate 시에도 _binding에 대입하도록 수정하세요

위의 ViewBinding 패턴 변경과 일관되게, binding 대신 _binding을 설정해야 합니다.

-        binding = FragmentChatRoomBinding.inflate(inflater, container, false)
+        _binding = FragmentChatRoomBinding.inflate(inflater, container, false)

99-100: onDestroyView에서 binding 해제 누락

ViewBinding 누수 방지를 위해 onDestroyView에서 _binding을 null로 해제해야 합니다.

     }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
+    }
 }
app/src/main/java/umc/onairmate/ui/profile/ProfileFragment.kt (1)

39-41: SharedPreferences null 강제 언래핑(!!) 제거

getString은 Nullable을 반환합니다. 현재 코드는 이론상 NPE 가능성이 있어 안전하지 않습니다. 엘비스 연산자로 기본값을 보장하세요.

-        val spf = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
-        nickname = spf.getString("nickname","user")!!
+        val spf = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
+        nickname = spf.getString("nickname", "user") ?: "user"
app/src/main/res/layout/fragment_profile.xml (1)

27-35: 아이콘 contentDescription 지정

접근성 향상을 위해 아이콘에 contentDescription을 지정하세요. 장식용이면 @null을 명시하세요.

 <ImageView
   android:id="@+id/iv_search"
   ...
-  />
+  android:contentDescription="@string/desc_search"/>
 
 <ImageView
   android:id="@+id/iv_alarm"
   ...
-  />
+  android:contentDescription="@string/desc_alarm"/>
 
 <ImageView
   android:id="@+id/iv_popularity_icon"
   ...
-  />
+  android:contentDescription="@null"/>
 
 <ImageView
   android:id="@+id/iv_tooltip"
   ...
-  />
+  android:contentDescription="@string/desc_tooltip"/>
 
 <ImageView ... android:src="@drawable/arrow"
-  />
+  android:contentDescription="@null"/>

추가 문자열:

  • desc_tooltip: 도움말

Also applies to: 38-45, 155-160, 171-176, 212-216, 234-238, 256-260, 278-282

🧹 Nitpick comments (33)
app/src/main/res/drawable/bg_btn_edit.xml (3)

2-2: 의도 불일치 가능성: edit 버튼 배경인데 비활성 색(@color/disable) 사용

파일명이 bg_btn_edit인데 색상은 disable을 참조하고 있습니다. 같은 PR에 bg_btn_disabled.xml도 있는 것으로 보이니, 이 리소스가 실제로 “편집 버튼 기본 상태”인지 “비활성 상태”인지 의도를 확인해 주세요. 의도가 비활성이라면 파일명을 bg_btn_edit_disabled 등으로 정정하거나, 의도가 활성 상태라면 적절한 브랜드/역할 색으로 교체하는 게 일관성 측면에서 낫습니다. 다크 모드에서도 대비가 유지되는지 함께 점검 권장.


3-3: 코너 반경 하드코딩(8dp) → dimen 토큰으로 추출 권장

여러 버튼 배경에서 동일한 반경을 쓸 가능성이 높습니다. @Dimen로 추출해 디자인 토큰화하면 유지보수성과 일관성이 좋아집니다.

적용 예시(리소스 존재 시):

-    <corners android:radius="8dp"/>
+    <corners android:radius="@dimen/corner_radius_8"/>

추가로 필요한 경우 res/values/dimens.xml에 다음을 정의하세요:

<dimen name="corner_radius_8">8dp</dimen>

1-4: 버튼 배경이라면 상태(selector) + ripple 대응 고려

현재 단일 shape만 있어 pressed/disabled/focused 등의 상태 피드백이 없습니다. Material 가이드를 따르려면 selector를 통해 상태별 배경(또는 ripple)을 적용하는 것이 좋습니다. bg_btn_edit.xml을 selector로 바꾸고, 실제 shape는 별도 파일로 분리하는 패턴을 추천합니다.

예시:

<!-- res/drawable/bg_btn_edit.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_enabled="false" android:drawable="@drawable/bg_btn_edit_disabled_shape"/>
    <item android:state_pressed="true" android:drawable="@drawable/bg_btn_edit_pressed_shape"/>
    <item android:drawable="@drawable/bg_btn_edit_normal_shape"/>
</selector>

Ripple을 사용할 경우:

<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="?attr/colorControlHighlight">
    <item android:drawable="@drawable/bg_btn_edit_normal_shape"/>
</ripple>
app/src/main/java/umc/OnAirMate/ui/chat_room/ChatRoomFragment.kt (1)

86-89: 빈 문자열(videoId == "") 처리 보강 제안

현재는 null만 기본값으로 대체합니다. 빈 문자열인 경우도 fallback되도록 하면 방어적입니다.

-                val videoId = roomData.videoId ?: "CgCVZdcKcqY"
+                val videoId = roomData.videoId?.takeIf { it.isNotBlank() } ?: "CgCVZdcKcqY"
app/src/main/res/drawable/btn_logout_outline.xml (1)

2-10: 터치 피드백(pressed/ripple) 추가 권장

커스텀 shape를 버튼 배경으로 쓰면 기본 리플이 사라질 수 있습니다. RippleDrawable로 감싸서 일관된 터치 피드백을 주는 것을 권장합니다.

아래와 같이 ripple로 감싸주세요:

-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-
-    <solid android:color="@android:color/transparent" />
-    <stroke
-        android:width="1dp"
-        android:color="@color/text5" />
-    <corners android:radius="6dp" />
-</shape>
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?attr/colorControlHighlight">
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/transparent" />
+            <stroke
+                android:width="1dp"
+                android:color="@color/text5" />
+            <corners android:radius="6dp" />
+        </shape>
+    </item>
+</ripple>
app/src/main/res/navigation/mobile_navigation.xml (2)

46-49: BlockListFragment에 라벨 추가 권장

툴즈 레이아웃만 지정되어 있고 라벨이 없어 툴바/접근성 측면에서 타이틀 표시에 누락이 생길 수 있습니다. 라벨을 추가해 주세요.

-    <fragment
+    <fragment
         android:id="@+id/blockListFragment"
         android:name="umc.onairmate.ui.profile.BlockListFragment"
-        tools:layout="@layout/fragment_block_list" />
+        android:label="@string/title_block_list"
+        tools:layout="@layout/fragment_block_list" />

strings.xml에 title_block_list 문자열 리소스 추가도 필요합니다.


37-44: 중복 내비게이션 그래프 정의 가능성 점검 필요

동일 목적지(ProfileFragment, BlockListFragment, 그리고 둘 사이 액션)가 nav_graph.xml에도 정의되어 있습니다. 서로 다른 NavHostFragment에서 각각의 그래프를 사용한다면 문제 없지만, 구성 변경이나 목적지 추가 시 그래프 간 정의가 어긋나 유지보수 비용과 런타임 혼동이 생길 수 있습니다. 단일 그래프로 통합하거나 include를 활용한 재사용을 고려해 주세요.

확인 포인트:

  • Profile → BlockList 네비게이션이 어느 NavHost에서 수행되는지.
  • mobile_navigation.xml과 nav_graph.xml 중 어떤 그래프가 실제 호스트에 로드되는지.
  • Safe Args를 사용한다면 두 그래프 모두에 동일 액션/목적지가 필요한지.

원하시면 그래프 분리/통합 전략 예시를 정리해 드리겠습니다.

Also applies to: 46-49

app/src/main/java/umc/OnAirMate/ui/profile/BlockListFragment.kt (2)

45-53: 디버그 전용 목업 데이터 가드 처리

테스트 데이터가 그대로 배포 빌드에 노출되지 않도록 DEBUG 빌드에서만 주입하도록 가드해 주세요.

-        // 테스트 데이터
-        blockListAdapter.submitList(
-            listOf(
-                BlockedUser("차단한 사용자 닉네임", "차단 사유", "0000년 00월 00일"),
-                BlockedUser("차단한 사용자 닉네임", "차단 사유", "0000년 00월 00일"),
-                BlockedUser("차단한 사용자 닉네임", "차단 사유", "0000년 00월 00일")
-            )
-        )
+        // 테스트 데이터 (DEBUG 빌드에서만)
+        if (BuildConfig.DEBUG) {
+            blockListAdapter.submitList(
+                listOf(
+                    BlockedUser("차단한 사용자 닉네임", "차단 사유", "0000년 00월 00일"),
+                    BlockedUser("차단한 사용자 닉네임", "차단 사유", "0000년 00월 00일"),
+                    BlockedUser("차단한 사용자 닉네임", "차단 사유", "0000년 00월 00일")
+                )
+            )
+        }

추가 import:

import umc.onairmate.BuildConfig

31-31: 디버그 로그 정리 제안

개발 로그는 BuildConfig.DEBUG로 가드하거나 Timber 등 로깅 유틸로 레벨 관리해 주세요.

-        Log.d("BlockListFragment", "view created, binding.ivBack=${binding.ivBack}")
+        if (BuildConfig.DEBUG) {
+            Log.d("BlockListFragment", "view created")
+        }
app/src/main/res/layout/item_blocked_user.xml (2)

8-15: 접근성: 프로필 아이콘 contentDescription 지정

장식용 이미지라면 contentDescription을 명시적으로 null 처리하세요. 스크린리더가 불필요한 정보를 읽지 않게 합니다.

     <ImageView
         android:id="@+id/iv_profile_icon"
         android:layout_width="40dp"
         android:layout_height="40dp"
         android:src="@drawable/profile"
+        android:contentDescription="@null"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintStart_toStartOf="parent" />

51-61: Material 컴포넌트 사용 및 i18n 반영 권장

MaterialButton으로 전환하면 테마 일관성과 접근성이 좋아집니다. 또한 하드코딩 텍스트는 strings.xml로 이관해 주세요.

-    <Button
+    <com.google.android.material.button.MaterialButton
         android:id="@+id/btnUnblock"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:text="차단 해제"
-        android:textSize="12sp"
-        android:textColor="@color/black"
-        android:backgroundTint="@color/sub"
+        style="@style/Widget.Material3.Button.OutlinedButton"
+        android:text="@string/blocklist_unblock"
+        android:textAllCaps="false"
+        android:textSize="12sp"
+        app:strokeColor="@color/text5"
+        app:strokeWidth="1dp"
+        app:cornerRadius="6dp"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent" />

참고:

  • strings.xml에 blocklist_unblock 문자열을 추가해야 합니다.
  • tvNickname/tvReason/tvDate의 플레이스홀더도 strings.xml로 이전하거나 tools:text로 대체하는 것을 권장합니다.
app/src/main/res/navigation/nav_graph.xml (3)

28-35: ProfileFragment 라벨 추가 권장

툴바 타이틀/접근성을 위해 라벨을 명시해 주세요.

     <fragment
         android:id="@+id/profileFragment"
         android:name="umc.onairmate.ui.profile.ProfileFragment"
-        tools:layout="@layout/fragment_profile">
+        android:label="@string/title_profile"
+        tools:layout="@layout/fragment_profile">

37-40: BlockListFragment 라벨 추가 권장

blockListFragment에도 라벨을 추가해 일관성을 맞춰 주세요.

     <fragment
         android:id="@+id/blockListFragment"
         android:name="umc.onairmate.ui.profile.BlockListFragment"
-        tools:layout="@layout/fragment_block_list" />
+        android:label="@string/title_block_list"
+        tools:layout="@layout/fragment_block_list" />

strings.xml에 title_block_list 추가 필요.


28-35: 두 그래프 간 목적지/액션 중복 정의 점검 필요

mobile_navigation.xml에도 동일 목적지/액션이 정의되어 있습니다. NavHost 구성이 분리되어 있지 않거나 향후 병합될 경우, 아이디 충돌/행동 불일치 위험이 있습니다. 단일 그래프로 통합하거나 include/ nested graph로 분리 재사용을 고려하세요.

원하시면:

  • 그래프 통합/분리 전략
  • action/animation 일관성 템플릿
    정리해서 PR에 반영할 수 있도록 제안 드리겠습니다.

Also applies to: 37-40

app/src/main/res/layout/fragment_block_list.xml (2)

47-58: 헤더 영역의 일관성/접근성 개선 제안

  • id 네이밍이 snake_case 기준에서 벗어납니다(ivBack). 일관성을 위해 iv_back로 변경 권장.
  • 헤더 터치 타겟이 작습니다. 헤더 전체(LinearLayout)를 클릭 가능하게 하고 최소 높이 48dp, 리플을 적용하세요.
-    <LinearLayout
-        android:id="@+id/layout_back_title"
+    <LinearLayout
+        android:id="@+id/layout_back_title"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
         android:gravity="center_vertical"
+        android:minHeight="48dp"
+        android:clickable="true"
+        android:focusable="true"
+        android:background="?attr/selectableItemBackground"
         app:layout_constraintTop_toBottomOf="@id/tv_app_title"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         android:layout_marginTop="20dp"
         android:layout_marginStart="16dp"
         android:layout_marginEnd="16dp">
 
-        <ImageView
-            android:id="@+id/ivBack"
+        <ImageView
+            android:id="@+id/iv_back"
             android:layout_width="24dp"
             android:layout_height="24dp"
             android:src="@drawable/ic_back"
             android:contentDescription="@string/desc_back" />

주의: id 변경 시 관련 코드/바인딩도 함께 변경 필요합니다.

Also applies to: 59-65


76-85: RecyclerView 기본 속성 보강 제안

시각적 완성도와 스크롤 경험 개선을 위해 다음 속성 추가를 권장합니다.

 <androidx.recyclerview.widget.RecyclerView
   android:id="@+id/rv_block_list"
   android:layout_width="0dp"
   android:layout_height="0dp"
   ...
-  android:padding="16dp"/>
+  android:padding="16dp"
+  android:clipToPadding="false"/>
app/src/main/java/umc/onairmate/ui/profile/ProfileFragment.kt (1)

9-9: 불필요/부정확한 import 제거

ContentProviderCompat.requireContext import는 본 클래스에서 사용되지 않으며 의미도 맞지 않습니다. 제거하세요.

-import androidx.core.content.ContentProviderCompat.requireContext
app/src/main/java/umc/OnAirMate/ui/profile/BlockListAdapter.kt (2)

10-15: 데이터 클래스 분리 권장

BlockedUser를 어댑터 파일에서 분리하면 재사용성과 테스트 용이성이 올라갑니다. 도메인/모델 패키지로 이동을 고려해 주세요.


28-36: 클릭 리스너 바인딩 방식 점검(미세 최적화/안전성)

현재 구현은 bind 호출마다 리스너를 새로 설정합니다. 성능상 문제는 미미하나, ViewHolder init에서 한 번만 설정하고 bindingAdapterPosition을 통해 아이템을 조회하면 재바인딩 비용을 줄이고 NO_POSITION 방어가 가능합니다.

-        fun bind(user: BlockedUser) {
-            binding.tvNickname.text = "[${user.nickname}]"
-            binding.tvReason.text = user.reason
-            binding.tvDate.text = user.date
-
-            binding.btnUnblock.setOnClickListener {
-                onUnblockClick(user)
-            }
-        }
+        init {
+            binding.btnUnblock.setOnClickListener {
+                val pos = bindingAdapterPosition
+                if (pos != RecyclerView.NO_POSITION) {
+                    onUnblockClick(getItem(pos))
+                }
+            }
+        }
+
+        fun bind(user: BlockedUser) {
+            binding.tvNickname.text = "[${user.nickname}]"
+            binding.tvReason.text = user.reason
+            binding.tvDate.text = user.date
+        }
app/src/main/res/layout/fragment_profile.xml (3)

7-9: 루트 레이아웃 marginTop 제거 권장

루트에 margin을 직접 두는 것은 전체 레이아웃 배치를 왜곡시킬 수 있습니다. 필요한 경우 내부 컨텐츠에 padding을 주거나 시스템 바 인셋 처리를 적용하세요.

-    android:background="@color/white"
-    android:layout_marginTop="20dp"
-    android:id="@+id/fragment_profile">
+    android:background="@color/white"
+    android:id="@+id/fragment_profile">

297-301: 하단 로고/로그아웃 버튼 레이아웃 유연성 개선

  • 로고 width를 470dp로 고정하면 작은 화면에서 깨질 수 있습니다. wrap_content 또는 최대 너비 제약을 권장합니다.
  • 임시 주석(✅ 여기 수정)은 커밋에서는 제거하세요.
-                <ImageView
+                <ImageView
                     android:id="@+id/onairmate_bottom_logo"
-                    android:layout_width="470dp"
-                    android:layout_height="89.26dp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
                     android:src="@drawable/logo_onairmate_profile"
                     android:layout_gravity="center"
                     android:layout_marginTop="50dp"/>

또한 레이아웃 주석의 "✅ 여기 수정" 부분은 삭제하세요.

-                android:layout_marginStart="16dp"> <!-- ✅ 여기 수정 -->
+                android:layout_marginStart="16dp">

Also applies to: 303-315


86-91: 고정 폭(dp) 사용 최소화

여러 컨테이너에 280dp 등 고정 폭을 사용하고 있습니다. 다양한 해상도 대응을 위해 match_parent + 좌우 margin을 권장합니다.

-    android:layout_width="280dp"
+    android:layout_width="match_parent"
+    android:layout_marginStart="16dp"
+    android:layout_marginEnd="16dp"

Also applies to: 118-121, 141-145, 181-186

app/src/main/res/drawable/bg_input_edit_text.xml (1)

2-4: EditText 테두리 두께가 너무 얇음 + 다크모드 비대응 색상 사용

  • 0.2dp 스트로크는 많은 디바이스에서 렌더링되지 않거나 거의 보이지 않습니다. 최소 1dp 권장.
  • 고정 색상(@color/white, @color/black)은 야간 모드에서 시인성이 깨질 수 있습니다. 테마 속성(예: ?attr/colorSurface, ?attr/colorOutline) 또는 Material 컴포넌트 사용을 권장합니다.

다음과 같이 최소 수정으로 가독성을 개선할 수 있습니다:

-    <solid android:color="@color/white" />
-    <stroke android:color="@color/black" android:width="0.2dp"/>
+    <solid android:color="?attr/colorSurface" />
+    <stroke android:color="@color/black" android:width="1dp"/>

추가로, 가능하면 TextInputLayout(Material)로 일관된 스타일/상태(포커스/에러)를 관리하는 방식을 검토해주세요.

app/src/main/java/umc/OnAirMate/data/repository/repository/NicknameRepository.kt (1)

3-5: 불리언 의미 명확화 또는 결과 모델 반환 고려

isNicknameDuplicated는 true=중복, false=사용가능을 의미합니다. 호출부 혼동을 줄이려면:

  • 메서드명을 isAvailable(반대 의미)로 정리하거나,
  • 에러/메시지까지 전달 가능한 결과 모델(예: data class NicknameCheck(val duplicated: Boolean, val message: String?)) 또는 Result<Boolean> 반환을 검토하세요.

현재 사용처가 단순 불리언이면 유지 가능하나, UI에서 서버 메시지를 노출한다면 결과 모델 방식이 더 확장적입니다.

app/src/main/res/layout/popup_change_nickname.xml (4)

29-31: Constraint 속성이 LinearLayout에 존재(무효 속성 정리 필요)

부모가 LinearLayout이므로 app:layout_constraint* 속성은 무시됩니다. 혼동을 줄이기 위해 제거하세요.

-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/title_image"

58-64: 닉네임 안내 TextView 고정 높이 제거 및 RTL 대응

고정 14dp 높이는 텍스트 잘림 위험. wrap_content 및 marginStart 사용 권장.

-                android:layout_height="14dp"
+                android:layout_height="wrap_content"
                 android:text="(3~10자 이내로 입력해주세요)"
                 android:textSize="12sp"
                 android:fontFamily="@font/pretendard_regular"
-                android:layout_marginLeft="3dp"/>
+                android:layout_marginStart="3dp"/>

81-85: layout_gravity="left" → "start"로 RTL 대응

-        android:layout_gravity="left">
+        android:layout_gravity="start">

95-105: ID와 텍스트의 의미 불일치 및 버튼 높이

id가 cancel_create인데 텍스트는 "완료"입니다. 의미가 일치하도록 리네이밍을 권장하며, 터치 타겟 높이도 보정하세요.

-    <androidx.appcompat.widget.AppCompatButton
-        android:id="@+id/cancel_create"
-        android:layout_width="50dp"
-        android:layout_height="20dp"
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_confirm"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
         android:background="@drawable/bg_btn_main"
         android:fontFamily="@font/pretendard_semi_bold"
         android:text="완료"
         android:textColor="@color/white"
         android:textSize="12sp"
         android:layout_gravity="center_horizontal"
         android:layout_marginTop="20dp"/>

주의: id 변경 시, 관련 바인딩/코드도 함께 수정해야 합니다.

app/src/main/java/umc/OnAirMate/ui/pop_up/ChangeNicknamePopup.kt (2)

61-68: 닉네임 공백 제거 및 i18n 문자열 리소스화

사용자 입력은 전후 공백을 제거하는 것이 안전합니다. 또한 하드코딩된 문자열은 strings.xml로 이동해 주세요.

-            val nickname = binding.editNickname.text.toString()
+            val nickname = binding.editNickname.text.toString().trim()

문자열:

  • "닉네임은 3자 이상 10자 이하여야 합니다." → @string/nickname_length_error

42-60: "완료" 버튼 동작 미정의

레이아웃에 "완료" 버튼이 있으나 클릭 리스너가 없습니다. 최소한 BottomSheet 닫기(dismiss) 또는 상위로 콜백을 제공하세요.

적용 예시(해당 범위 외 코드 참조용):

binding.btnConfirm.setOnClickListener {
    // 필요 시 닉네임 값 전달 콜백 추가
    dismiss()
}

ID를 btn_confirm로 변경하는 경우, 레이아웃과 코드 모두 일관되게 수정하세요.

app/src/main/java/umc/OnAirMate/data/repository/repositoryImpl/NicknameRepositoryImpl.kt (3)

8-10: 레포지토리 스코프 보장(@singleton) 검토

Repository는 무상태/공유가 일반적이므로 @singleton 스코프로 관리하는 것이 바람직합니다. Module에서 @Binds/@provides@singleton이 지정돼 있지 않다면, 구현체에 @singleton을 부여해 단일 인스턴스를 보장해 주세요.

적용 예:

+@Singleton
 class NicknameRepositoryImpl @Inject constructor(
     private val api: NicknameService
 ): NicknameRepository {

필요한 import:

import javax.inject.Singleton

12-12: 사전 유효성 검사(트리밍/빈 문자열) 고려

서버 스펙에 따라 다르겠지만, 공백만 입력되거나 앞뒤 공백이 포함된 닉네임은 클라이언트에서 사전에 정규화/차단하는 편이 낭비 호출을 줄입니다. 예: api.checkNickname(nickname.trim()) 또는 blank면 바로 false/에러 반환.


12-21: 단위 테스트 보강 제안(정상/실패/예외 케이스)

다음 시나리오 별 검증을 권장합니다:

  • success=true, available=true → false(중복 아님)
  • success=true, available=false → true(중복)
  • success=false → false(현재 로직 기준) 또는 Error(Result) 설계 시 에러 전파
  • HttpException/IOException 발생 → false(현재) 또는 Error(Result)

Mock/Fake NicknameService로 빠르게 커버 가능합니다. 필요하시면 테스트 스켈레톤 생성 도와드리겠습니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01b6895 and fb07c17.

📒 Files selected for processing (22)
  • app/src/main/java/umc/OnAirMate/data/api/NicknameService.kt (1 hunks)
  • app/src/main/java/umc/OnAirMate/data/model/entity/NicknameData.kt (1 hunks)
  • app/src/main/java/umc/OnAirMate/data/model/response/NicknameResponse.kt (1 hunks)
  • app/src/main/java/umc/OnAirMate/data/repository/repository/NicknameRepository.kt (1 hunks)
  • app/src/main/java/umc/OnAirMate/data/repository/repositoryImpl/NicknameRepositoryImpl.kt (1 hunks)
  • app/src/main/java/umc/OnAirMate/ui/chat_room/ChatRoomFragment.kt (1 hunks)
  • app/src/main/java/umc/OnAirMate/ui/pop_up/ChangeNicknamePopup.kt (1 hunks)
  • app/src/main/java/umc/OnAirMate/ui/pop_up/ChangeNicknameViewModel.kt (1 hunks)
  • app/src/main/java/umc/OnAirMate/ui/profile/BlockListAdapter.kt (1 hunks)
  • app/src/main/java/umc/OnAirMate/ui/profile/BlockListFragment.kt (1 hunks)
  • app/src/main/java/umc/onairmate/module/RepositoryModule.kt (2 hunks)
  • app/src/main/java/umc/onairmate/module/ServiceModule.kt (2 hunks)
  • app/src/main/java/umc/onairmate/ui/profile/ProfileFragment.kt (3 hunks)
  • app/src/main/res/drawable/bg_btn_edit.xml (1 hunks)
  • app/src/main/res/drawable/bg_input_edit_text.xml (1 hunks)
  • app/src/main/res/drawable/btn_logout_outline.xml (1 hunks)
  • app/src/main/res/layout/fragment_block_list.xml (1 hunks)
  • app/src/main/res/layout/fragment_profile.xml (4 hunks)
  • app/src/main/res/layout/item_blocked_user.xml (1 hunks)
  • app/src/main/res/layout/popup_change_nickname.xml (1 hunks)
  • app/src/main/res/navigation/mobile_navigation.xml (1 hunks)
  • app/src/main/res/navigation/nav_graph.xml (1 hunks)
🧰 Additional context used
🪛 detekt (1.23.8)
app/src/main/java/umc/OnAirMate/ui/pop_up/ChangeNicknameViewModel.kt

[warning] 24-24: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🔇 Additional comments (14)
app/src/main/res/drawable/bg_btn_edit.xml (1)

1-4: 기본 구조는 문제 없음

단일 배경 리소스로만 쓰는 경우 현재 shape 정의는 정상 동작합니다.

app/src/main/java/umc/OnAirMate/ui/chat_room/ChatRoomFragment.kt (1)

86-90: onReady 시그니처 수정은 적절합니다

잘못된 문법(백슬래시) 제거와 올바른 override 시그니처 반영이 정확합니다. 별도 부작용 없어 보입니다.

app/src/main/res/navigation/mobile_navigation.xml (1)

40-44: 프로필 → 차단목록 내비게이션 액션 추가: 방향성 좋습니다

ProfileFragment에서 BlockListFragment로의 액션 정의가 적절합니다. 관련 화면 전환이 분리된 그래프 내에서 명확합니다.

app/src/main/res/navigation/nav_graph.xml (1)

28-35: Profile → BlockList 내비게이션 추가: 전체 플로우 연결 확인

상세 플로우가 nav_graph에도 반영되어 있어 상호 참조가 명확합니다.

app/src/main/java/umc/onairmate/ui/profile/ProfileFragment.kt (1)

71-77: 차단 리스트 내비게이션 LGTM

새 내비게이션 액션을 통해 BlockListFragment로 이동하는 처리 적절합니다.

app/src/main/java/umc/OnAirMate/ui/profile/BlockListAdapter.kt (1)

39-44: Diff 비교 키 검증 요청(닉네임의 유일성 보장?)

areItemsTheSame에서 nickname으로 동일성 판단을 합니다. 닉네임이 시스템에서 유일 식별자인지 확인이 필요합니다. 유일하지 않다면 내부 id(예: userId)를 사용해야 안전합니다.

app/src/main/java/umc/OnAirMate/data/model/entity/NicknameData.kt (1)

5-11: 서버 응답의 message 필드 null 가능성 검토

백엔드에서 message가 상황에 따라 누락/null일 수 있다면 String 대신 String?로 두거나, 역직렬화 예외 방지를 위한 기본값 제공이 필요할 수 있습니다. 실제 응답 스키마를 확인해 주세요.

필요 시 적용 가능한 변경 예:

-    @SerializedName("message")
-    val message: String
+    @SerializedName("message")
+    val message: String?
app/src/main/java/umc/onairmate/module/ServiceModule.kt (1)

49-53: NicknameService DI 등록 일관성 유지 — LGTM

Retrofit 기반의 Service 제공 메서드 패턴과 스코프(@singleton)가 기존과 동일하며, 네이밍도 일관적입니다.

app/src/main/java/umc/OnAirMate/data/api/NicknameService.kt (1)

11-16: Retrofit 인터페이스 정의 적절 — LGTM

suspend 함수 시그니처와 Path 바인딩이 명확하며, 반환 타입도 DTO와 일치합니다.

app/src/main/java/umc/onairmate/module/RepositoryModule.kt (2)

52-57: DI 바인딩 스코프와 구현체 연결 적절 — LGTM

ViewModelScoped 스코프, 생성자 주입 방식이 기존 레이어와 일치합니다.


13-13: NicknameRepository import 경로 확인 완료

NicknameRepository.ktpackageumc.onairmate.data.repository로 선언되어 있으므로, 현재의

import umc.onairmate.data.repository.NicknameRepository

구문은 올바릅니다. 변경이 필요 없습니다.

Likely an incorrect or invalid review comment.

app/src/main/java/umc/OnAirMate/ui/pop_up/ChangeNicknameViewModel.kt (1)

8-13: NicknameRepository import 경로 검증 결과: 변경 불필요

검증 결과, NicknameRepository.kt의 패키지 선언(package umc.onairmate.data.repository)과 뷰모델에서 사용 중인
import umc.onairmate.data.repository.NicknameRepository 경로가 일치합니다.
따라서 import를 수정할 필요가 없습니다.

Likely an incorrect or invalid review comment.

app/src/main/java/umc/OnAirMate/data/repository/repositoryImpl/NicknameRepositoryImpl.kt (2)

15-16: 가용성 플래그 반전 로직 적절

API의 available=true(사용 가능) → 중복 아님, 따라서 중복 여부는 !available 로 계산하는 현재 로직은 맞습니다.


3-3: android.util.Log 사용 일관성 확인됨
현재 프로젝트 전반에 Timber 사용 흔적이 없으며, 모든 로깅이 android.util.Log로 통일되어 있습니다. 별도의 변경은 필요하지 않습니다.
추후에 Timber 도입을 검토하신다면 그때 일관성 있게 마이그레이션하시길 권장드립니다.

@@ -0,0 +1,16 @@
package umc.onairmate.data.api
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

패키지 경로 대소문자 불일치 가능성 점검

파일 시스템 경로(app/src/main/java/umc/OnAirMate/…)와 선언 패키지(package umc.onairmate…)의 대소문자 불일치가 보입니다. CI(리눅스 등 대소문자 구분) 환경에서 중복 패키지/클래스 인식 문제를 유발할 수 있어 디렉터리명을 패키지 규약(all-lowercase)으로 통일하는 것을 권장합니다.

다음 스크립트로 대소문자 혼재 여부와 해당 파일들의 package 선언을 확인해 주세요.


🏁 Script executed:

#!/bin/bash
# 대소문자 혼재 디렉터리 나열
echo "[디렉터리 확인]"
fd -t d -a 'OnAirMate|onairmate' app/src/main/java/umc | sort

# OnAirMate 경로를 사용하는 파일들의 package 선언 확인
echo
echo "[OnAirMate 경로 내 Kotlin 파일의 package 선언]"
fd -a -e kt 'app/src/main/java/umc/OnAirMate' | while read -r f; do
  echo "=== $f"
  rg -n '^package ' "$f" -n || true
done

Length of output: 779


추가로 OnAirMate 디렉터리 내 Kotlin 파일들의 패키지 선언을 확인합니다:


🏁 Script executed:

#!/bin/bash
echo "[OnAirMate 경로 내 Kotlin 파일 목록]"
find app/src/main/java/umc/OnAirMate -type f -name '*.kt' | sort

echo
echo "[OnAirMate 경로 내 Kotlin 파일들의 package 선언]"
find app/src/main/java/umc/OnAirMate -type f -name '*.kt' | while IFS= read -r f; do
  echo "=== $f"
  grep -n '^package ' "$f" || true
done

Length of output: 15518


패키지 디렉터리명 소문자 통일 필요

app/src/main/java/umc 경로에 대문자 혼용 디렉터리(OnAirMate)가 존재하여 선언된 패키지 umc.onairmate와 불일치합니다. Linux CI 환경에서 클래스 인식 문제를 방지하려면 디렉터리명을 모두 소문자로 변경하세요.

  • 변경 전: app/src/main/java/umc/OnAirMate/...
  • 변경 후: app/src/main/java/umc/onairmate/...
🤖 Prompt for AI Agents
In app/src/main/java/umc/OnAirMate/data/api/NicknameService.kt around line 1,
the package declaration uses lowercase "umc.onairmate" but the filesystem
directory is "OnAirMate" (mixed case), causing mismatch on case-sensitive CI;
rename the directory app/src/main/java/umc/OnAirMate to
app/src/main/java/umc/onairmate (use git mv to preserve history), ensure all
package declarations and imports still match "umc.onairmate", commit the change,
and run a build to verify no remaining case-related path errors.

@@ -0,0 +1,12 @@
package umc.onairmate.data.model.response
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

패키지-디렉터리 대소문자 일치 검증 요청

해당 파일 경로는 …/umc/OnAirMate/…인데 package는 umc.onairmate…입니다. 대소문자 혼재는 환경에 따라 문제를 유발할 수 있으니 디렉터리 표준화(all-lowercase) 여부 확인 바랍니다.

다음 스크립트로 확인 가능합니다:


🏁 Script executed:

#!/bin/bash
echo "[NicknameResponse.kt package 확인]"
fd -a 'NicknameResponse.kt' | while read -r f; do
  echo "=== $f"
  rg -n '^package ' "$f" -n || true
done

Length of output: 483


디렉터리 이름 소문자 일치 필요: OnAirMateonairmate로 변경

JVM 패키지-디렉터리 매핑에서 대소문자 불일치는 빌드 또는 런타임 오류를 유발할 수 있습니다. package umc.onairmate.data.model.response 선언과 실제 경로가 일치하도록 디렉터리를 모두 소문자로 표준화해주세요.

  • 수정 대상
    • 기존 경로:
      app/src/main/java/umc/OnAirMate/data/model/response/NicknameResponse.kt
    • 변경 경로 예시:
      app/src/main/java/umc/onairmate/data/model/response/NicknameResponse.kt
  • 작업 내용
    1. umc/OnAirMate/… 디렉터리 이름을 umc/onairmate/…로 변경
    2. 변경된 경로에 맞춰 프로젝트 내 패키지 선언과 import 경로 검토 및 업데이트

다른 패키지 경로에도 동일한 대소문자 불일치가 없는지 추가 확인 바랍니다.

🤖 Prompt for AI Agents
In app/src/main/java/umc/OnAirMate/data/model/response/NicknameResponse.kt
around line 1, the directory name casing (`OnAirMate`) does not match the
package declaration `umc.onairmate...`, which can break JVM package-directory
mapping; rename the directory path from umc/OnAirMate/... to umc/onairmate/...
and move the file to
app/src/main/java/umc/onairmate/data/model/response/NicknameResponse.kt, then
update any affected package/import references across the project to use the
all-lowercase `umc.onairmate` path and verify there are no other casing
mismatches in source directories.

Comment on lines 6 to 12
class NicknameResponse (
@SerializedName("success")
val success: Boolean,

@SerializedName("data")
val data: NicknameData
) No newline at end of file
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

DTO는 data class 사용 권장

동등성/복사/디버깅에 유리하도록 data class로 선언을 권장합니다.

다음 diff를 적용해 주세요:

-class NicknameResponse (
+data class NicknameResponse (
     @SerializedName("success")
     val success: Boolean,

     @SerializedName("data")
     val data: NicknameData
 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class NicknameResponse (
@SerializedName("success")
val success: Boolean,
@SerializedName("data")
val data: NicknameData
)
data class NicknameResponse (
@SerializedName("success")
val success: Boolean,
@SerializedName("data")
val data: NicknameData
)
🤖 Prompt for AI Agents
In app/src/main/java/umc/OnAirMate/data/model/response/NicknameResponse.kt
around lines 6 to 12, the DTO is declared as a regular class but should be a
data class to get automatic equals/hashCode/toString/copy; change the
declaration from "class NicknameResponse (...)" to "data class NicknameResponse
(...)" leaving the existing @SerializedName annotations and property definitions
unchanged so it becomes a Kotlin data class.

Comment on lines 1 to 5
package umc.onairmate.data.repository

interface NicknameRepository {
suspend fun isNicknameDuplicated(nickname: String): Boolean
} No newline at end of file
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

패키지-디렉터리 경로 불일치 가능성(OnAirMate vs onairmate, repository/repository)

파일 경로가 .../data/repository/repository/NicknameRepository.kt인데, 패키지는 umc.onairmate.data.repository입니다. 대소문자(OnAirMate vs onairmate)와 디렉터리 깊이 차이(repository/repository)가 섞여 있으면, OS에 따라 빌드/IDE 인덱싱 이슈가 발생할 수 있습니다. 전체 모듈에서 일관된 디렉터리 구조 사용을 권장합니다.

다음 스크립트로 대소문자 혼용 및 중복 디렉터리 여부를 점검해 주세요:


🏁 Script executed:

#!/bin/bash
# 대소문자 혼용 디렉터리 탐지
echo "Directories containing 'OnAirMate' (capitalized):"
fd OnAirMate -t d

echo
echo "Directories containing 'onairmate' (lowercase):"
fd onairmate -t d

echo
echo "Check for duplicated 'repository/repository' paths:"
fd -t f 'NicknameRepository.kt' -x dirname {} | sort -u

Length of output: 691


패키지 선언 및 디렉터리 구조 정합성 확보 필요

현재 NicknameRepository.kt 파일이 아래 경로에 위치하면서 패키지 선언(umc.onairmate.data.repository)과 실제 디렉터리 구조가 일치하지 않습니다.
OS나 IDE 인덱싱 이슈를 방지하려면 다음 작업이 필요합니다:

  • 대상 파일
    • app/src/main/java/umc/OnAirMate/data/repository/repository/NicknameRepository.kt
  • 조치 사항
    1. umc/OnAirMate 디렉터리명을 기존 테스트 코드(app/src/test/java/umc/onairmate, app/src/androidTest/java/umc/onairmate)와 동일하게 모두 소문자 onairmate로 변경
    2. 중복된 repository/repository 폴더 구조를 제거하여 최종 경로를
      app/src/main/java/umc/onairmate/data/repository/NicknameRepository.kt
      형태로 통일
    3. 변경 후 패키지 선언(package umc.onairmate.data.repository)이 실제 경로와 완전히 일치하는지 확인

위 조치로 대소문자 혼용 및 중복 디렉터리 문제를 해소하고, 빌드/IDE 환경 전반의 안정성을 확보할 수 있습니다.

🤖 Prompt for AI Agents
In
app/src/main/java/umc/OnAirMate/data/repository/repository/NicknameRepository.kt
lines 1-5, the package declaration and filesystem path do not match due to a
capitalized folder and duplicate "repository" directory; rename the directory
"OnAirMate" to all-lowercase "onairmate", remove the extra nested "repository"
folder so the file resides at
app/src/main/java/umc/onairmate/data/repository/NicknameRepository.kt, and
verify the file's package declaration remains "package
umc.onairmate.data.repository" (update it if necessary) so the package and path
are fully consistent.

@@ -0,0 +1,23 @@
package umc.onairmate.data.repository.repositoryImpl
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

패키지-디렉터리 대소문자 불일치 가능성 점검

파일 경로에는 'OnAirMate'(대문자) 디렉터리가 보이고, 패키지는 'umc.onairmate'(소문자)입니다. Linux와 같이 대소문자 구분 파일시스템 환경에서 빌드 이슈가 발생할 수 있으니, 디렉터리/패키지 네이밍 일관성을 확인해 주세요.

점검 스크립트:


🏁 Script executed:

#!/bin/bash
# 패키지 선언 케이스 통계
echo "[package umc.onairmate] count:"
rg -n '^package umc\.onairmate' | wc -l
echo "[package umc.OnAirMate] count:"
rg -n '^package umc\.OnAirMate' | wc -l

# 디렉터리 케이스 확인
echo "[Directories named onairmate]"
fd -t d -a "onairmate" app/src/main/java/umc || true
echo "[Directories named OnAirMate]"
fd -t d -a "OnAirMate" app/src/main/java/umc || true

# import/참조에서 케이스 혼용 여부
rg -n 'umc\.OnAirMate' -S || true

Length of output: 650


패키지/디렉터리 네이밍 불일치 해결

Linux 환경에서 빌드 오류를 방지하려면 app/src/main/java/umc 경로에 중복으로 존재하는 OnAirMate/onairmate/ 디렉터리 중 하나를 삭제하거나 이름을 통일하세요. 모든 package umc.onairmate 선언이 소문자를 전제하므로:

  • OnAirMate/ 디렉터리를 onairmate/로 리네임
  • 또는 불필요한 디렉터리(중복 경로) 삭제

수정 후 import 및 패키지 경로가 모두 umc.onairmate(소문자)로 참조되는지 재확인 바랍니다.

🤖 Prompt for AI Agents
In
app/src/main/java/umc/OnAirMate/data/repository/repositoryImpl/NicknameRepositoryImpl.kt
around line 1, there is a package/directory name mismatch between `OnAirMate`
and `onairmate` causing Linux build errors; resolve it by renaming the
filesystem directory `OnAirMate` to `onairmate` (or removing the duplicate
directory) so the physical path matches the package declaration `package
umc.onairmate...`, then update and verify all affected package declarations and
import statements across the project to use the lowercase `umc.onairmate` path.

Comment on lines 16 to 25
<LinearLayout
android:id="@+id/layout_texts"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintStart_toEndOf="@id/iv_profile_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="8dp">

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Constraint 세팅 누락: 0dp 폭 사용 시 End 제약 필요

layout_texts가 width=0dp인데 end 제약이 없어 실제 폭 계산이 비정상일 수 있습니다. btnUnblock 쪽으로 end 제약을 추가하세요.

     <LinearLayout
         android:id="@+id/layout_texts"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:orientation="vertical"
         app:layout_constraintStart_toEndOf="@id/iv_profile_icon"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginStart="8dp">
+        app:layout_constraintEnd_toStartOf="@id/btnUnblock"
+        android:layout_marginStart="8dp"
+        android:layout_marginEnd="8dp">
🤖 Prompt for AI Agents
In app/src/main/res/layout/item_blocked_user.xml around lines 16 to 25,
layout_texts uses android:layout_width="0dp" but has no end constraint, causing
incorrect width calculation; add an end constraint pointing to the unblock
button (e.g. app:layout_constraintEnd_toStartOf="@id/btnUnblock") so the 0dp
width is resolved between the profile icon and the button, and ensure any
required margin/padding is applied.

Comment on lines +4 to +5
android:layout_width="340dp"
android:layout_height="220dp"
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

고정 폭/높이로 인한 반응형/접근성 문제

루트 레이아웃을 340x220dp 고정으로 두면 다양한 해상도에서 깨질 수 있습니다. BottomSheet는 보통 width=match_parent, height=wrap_content로 둡니다.

-    android:layout_width="340dp"
-    android:layout_height="220dp"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
android:layout_width="340dp"
android:layout_height="220dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
🤖 Prompt for AI Agents
In app/src/main/res/layout/popup_change_nickname.xml around lines 4 to 5, the
root layout currently uses fixed android:layout_width="340dp" and
android:layout_height="220dp", which breaks responsiveness and accessibility;
change them to android:layout_width="match_parent" and
android:layout_height="wrap_content" (optionally constrain with a maxWidth or
side margins for large screens and use minHeight if needed) so the BottomSheet
adapts to different screen sizes.

Comment on lines +8 to +11
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

RTL 대응 미흡: paddingLeft/Right 대신 paddingStart/End 사용

국제화/RTL 지원을 위해 start/end 속성 사용을 권장합니다.

-    android:paddingLeft="20dp"
-    android:paddingRight="20dp"
+    android:paddingStart="20dp"
+    android:paddingEnd="20dp"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
🤖 Prompt for AI Agents
In app/src/main/res/layout/popup_change_nickname.xml around lines 8 to 11, the
layout uses android:paddingLeft and android:paddingRight which should be
replaced with android:paddingStart and android:paddingEnd to support RTL; update
the attributes to use paddingStart="20dp" and paddingEnd="20dp" (keep paddingTop
and paddingBottom unchanged) and remove the left/right attributes so the view
respects layout direction.

Comment on lines +49 to +55
android:layout_width="180dp"
android:layout_height="20dp"
android:background="@drawable/bg_input_edit_text"
android:maxLines="1"
android:textSize="14sp"
android:layout_marginLeft="3dp"/>

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

EditText 접근성/사용성 개선: 너무 낮은 높이(20dp), 입력 힌트/타입/최대길이 미설정

  • 터치 타겟 최소 48dp 권장(Material). 20dp는 접근성 기준 미달.
  • hint, inputType, maxLength로 UX 개선 및 제약을 UI 레벨에서 보장 필요.
-                android:layout_height="20dp"
+                android:layout_height="48dp"
                 android:background="@drawable/bg_input_edit_text"
-                android:maxLines="1"
+                android:maxLines="1"
+                android:hint="닉네임 입력"
+                android:inputType="textPersonName"
+                android:maxLength="10"
                 android:textSize="14sp"
-                android:layout_marginLeft="3dp"/>
+                android:layout_marginStart="3dp"/>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
android:layout_width="180dp"
android:layout_height="20dp"
android:background="@drawable/bg_input_edit_text"
android:maxLines="1"
android:textSize="14sp"
android:layout_marginLeft="3dp"/>
android:layout_width="180dp"
android:layout_height="48dp"
android:background="@drawable/bg_input_edit_text"
android:maxLines="1"
android:hint="닉네임 입력"
android:inputType="textPersonName"
android:maxLength="10"
android:textSize="14sp"
android:layout_marginStart="3dp"/>
🤖 Prompt for AI Agents
In app/src/main/res/layout/popup_change_nickname.xml around lines 49 to 55, the
EditText uses a 20dp height and lacks hint, inputType, and maxLength which fails
accessibility and UX; change the height to at least 48dp (or set
android:minHeight="48dp" and appropriate padding) to meet touch-target
guidelines, add a descriptive android:hint (e.g., "Enter nickname"), set
android:inputType="textPersonName" (or "text"), keep android:maxLines="1", and
add an android:maxLength value (e.g., 20) to enforce a reasonable nickname limit
at the UI level.

Comment on lines +66 to +76
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/check_nickname"
android:layout_width="50dp"
android:layout_height="20dp"
android:background="@drawable/bg_btn_disabled"
android:gravity="center"
android:text="중복 확인"
android:textSize="12sp"
android:fontFamily="@font/pretendard_medium"
android:textColor="@color/white"
android:layout_marginLeft="5dp"/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

버튼 터치 타겟/상태 색상

  • 높이 20dp는 접근성 기준(48dp) 미달.
  • disabled 상태에서 흰색 텍스트는 대비가 부족할 수 있습니다. 상태별 색상/알파를 재검토하세요.
-            android:layout_height="20dp"
+            android:layout_height="48dp"
             android:background="@drawable/bg_btn_disabled"
             android:gravity="center"
             android:text="중복 확인"
             android:textSize="12sp"
             android:fontFamily="@font/pretendard_medium"
-            android:textColor="@color/white"
-            android:layout_marginLeft="5dp"/>
+            android:textColor="@color/white"
+            android:layout_marginStart="5dp"/>

추가로 bg_btn_disabled가 충분한 명도 대비를 제공하는지 확인해 주세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/check_nickname"
android:layout_width="50dp"
android:layout_height="20dp"
android:background="@drawable/bg_btn_disabled"
android:gravity="center"
android:text="중복 확인"
android:textSize="12sp"
android:fontFamily="@font/pretendard_medium"
android:textColor="@color/white"
android:layout_marginLeft="5dp"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/check_nickname"
android:layout_width="50dp"
android:layout_height="48dp"
android:background="@drawable/bg_btn_disabled"
android:gravity="center"
android:text="중복 확인"
android:textSize="12sp"
android:fontFamily="@font/pretendard_medium"
android:textColor="@color/white"
android:layout_marginStart="5dp"/>
🤖 Prompt for AI Agents
In app/src/main/res/layout/popup_change_nickname.xml around lines 66 to 76, the
button's touch target and disabled-state contrast are inadequate: change the
height to meet accessibility (use android:layout_height="48dp" or set
android:minHeight="48dp" and add sufficient vertical padding) and replace the
hardcoded textColor with a colorStateList that provides distinct, high-contrast
colors for enabled/disabled states; also update bg_btn_disabled or its drawable
to ensure sufficient luminance contrast with the disabled text (adjust
color/alpha or provide a separate disabled background) and verify contrast
ratios meet accessibility guidelines.

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

🧹 Nitpick comments (2)
app/src/main/res/layout/item_blocked_user.xml (2)

20-29: 닉네임과 더보기 버튼 간 간격 확보

닉네임이 0dp 폭으로 버튼까지 확장되므로, 버튼과의 시각적 간격을 위해 End 마진을 추가하는 것이 좋습니다.

     <TextView
         android:id="@+id/tvNickname"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:text="[차단한 사용자 닉네임]"
         android:textSize="14sp"
         android:textStyle="bold"
         app:layout_constraintTop_toTopOf="@id/iv_profile_icon"
         app:layout_constraintStart_toEndOf="@id/iv_profile_icon"
         app:layout_constraintEnd_toStartOf="@id/btnMore"
-        android:layout_marginStart="8dp" />
+        android:layout_marginStart="8dp"
+        android:layout_marginEnd="8dp" />

37-38: 다크 테마 대응: 하드코딩 색상 제거

@color/black 하드코딩은 다크 테마에서 가독성을 해칠 수 있습니다. 테마 속성 사용을 권장합니다. 위 다른 코멘트의 diff에 포함된 대로 colorOnSurface로 교체하세요.

Also applies to: 50-51

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb07c17 and e364f12.

📒 Files selected for processing (5)
  • app/src/main/java/umc/OnAirMate/ui/profile/BlockListAdapter.kt (1 hunks)
  • app/src/main/java/umc/OnAirMate/ui/profile/BlockListFragment.kt (1 hunks)
  • app/src/main/res/drawable/ic_more_vert_black_24dp.xml (1 hunks)
  • app/src/main/res/layout/item_blocked_user.xml (1 hunks)
  • app/src/main/res/menu/menu_block_option.xml (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • app/src/main/res/menu/menu_block_option.xml
  • app/src/main/res/drawable/ic_more_vert_black_24dp.xml
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/java/umc/OnAirMate/ui/profile/BlockListFragment.kt
  • app/src/main/java/umc/OnAirMate/ui/profile/BlockListAdapter.kt

Comment on lines +1 to +7
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

플레이스홀더 텍스트는 tools:text로 전환 및 tools 네임스페이스 선언

런타임에 더미 텍스트가 노출되지 않도록 android:text 대신 tools:text를 사용하세요. 이를 위해 tools 네임스페이스를 추가해야 합니다.

 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:padding="8dp">
@@
     <TextView
         android:id="@+id/tvNickname"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:text="[차단한 사용자 닉네임]"
+        tools:text="[차단한 사용자 닉네임]"
         android:textSize="14sp"
         android:textStyle="bold"
@@
     <TextView
         android:id="@+id/tvReason"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:text="[차단 사유]"
+        tools:text="[차단 사유]"
         android:textSize="12sp"
@@
     <TextView
         android:id="@+id/tvDate"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:text="[차단한 일자 (0000년 00월 00일)]"
+        tools:text="[차단한 일자 (0000년 00월 00일)]"
         android:textSize="12sp"

Also applies to: 19-30, 31-43, 45-55

🤖 Prompt for AI Agents
In app/src/main/res/layout/item_blocked_user.xml around lines 1-7 (and also
apply the same change across lines 19-30, 31-43, and 45-55), replace any
android:text attributes used for placeholder/demo text with tools:text and add
the tools namespace declaration (xmlns:tools="http://schemas.android.com/tools")
to the root ConstraintLayout; ensure only design-time placeholder text uses
tools:text so no dummy text appears at runtime.

Comment on lines +10 to +16
<ImageView
android:id="@+id/iv_profile_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/profile"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

장식용 프로필 아이콘은 접근성에서 무시되도록 설정

화면 리더가 불필요하게 읽지 않도록 장식용 이미지는 contentDescription을 null 처리하세요.

     <ImageView
         android:id="@+id/iv_profile_icon"
         android:layout_width="40dp"
         android:layout_height="40dp"
         android:src="@drawable/profile"
+        android:contentDescription="@null"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintStart_toStartOf="parent" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<ImageView
android:id="@+id/iv_profile_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/profile"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/iv_profile_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/profile"
android:contentDescription="@null"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
🤖 Prompt for AI Agents
In app/src/main/res/layout/item_blocked_user.xml around lines 10 to 16, the
ImageView is decorative and must be ignored by accessibility services; add
android:contentDescription="@null" to the ImageView (and optionally set
android:importantForAccessibility="no" if your project requires) so screen
readers will not announce the decorative profile icon.

Comment on lines +31 to +42
<!-- 차단 사유 -->
<TextView
android:id="@+id/tvReason"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="[차단 사유]"
android:textSize="12sp"
android:textColor="@color/black"
app:layout_constraintTop_toBottomOf="@id/iv_profile_icon"
app:layout_constraintStart_toStartOf="@id/iv_profile_icon"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="4dp" />
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

tvReason 제약 오류: 아이콘 아래/왼쪽에 붙는 레이아웃 깨짐

tvReason이 iv_profile_icon 기준으로 Top/Start가 잡혀 있어 텍스트가 아이콘 아래로 내려가고, End도 parent로 잡혀 btnMore와 겹칠 수 있습니다. 닉네임 아래, 버튼 왼쪽에 오도록 제약을 수정하세요.

아래 diff 적용을 제안합니다:

     <TextView
         android:id="@+id/tvReason"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:text="[차단 사유]"
         android:textSize="12sp"
-        android:textColor="@color/black"
-        app:layout_constraintTop_toBottomOf="@id/iv_profile_icon"
-        app:layout_constraintStart_toStartOf="@id/iv_profile_icon"
-        app:layout_constraintEnd_toEndOf="parent"
+        android:textColor="?attr/colorOnSurface"
+        app:layout_constraintTop_toBottomOf="@id/tvNickname"
+        app:layout_constraintStart_toStartOf="@id/tvNickname"
+        app:layout_constraintEnd_toStartOf="@id/btnMore"
         android:layout_marginTop="4dp" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<!-- 차단 사유 -->
<TextView
android:id="@+id/tvReason"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="[차단 사유]"
android:textSize="12sp"
android:textColor="@color/black"
app:layout_constraintTop_toBottomOf="@id/iv_profile_icon"
app:layout_constraintStart_toStartOf="@id/iv_profile_icon"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="4dp" />
<!-- 차단 사유 -->
<TextView
android:id="@+id/tvReason"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="[차단 사유]"
android:textSize="12sp"
android:textColor="?attr/colorOnSurface"
app:layout_constraintTop_toBottomOf="@id/tvNickname"
app:layout_constraintStart_toStartOf="@id/tvNickname"
app:layout_constraintEnd_toStartOf="@id/btnMore"
android:layout_marginTop="4dp" />
🤖 Prompt for AI Agents
In app/src/main/res/layout/item_blocked_user.xml around lines 31 to 42, tvReason
is constrained to iv_profile_icon Top/Start and End=parent which makes it sit
under the icon and can overlap btnMore; change its constraints to sit under the
nickname and to the left of the more button by setting
app:layout_constraintTop_toBottomOf to the nickname TextView (e.g.,
@id/tvNickname), app:layout_constraintStart_toStartOf to the nickname (not the
icon), and app:layout_constraintEnd_toStartOf to the more button (e.g.,
@id/btnMore); keep a small top margin (4dp) and optionally a marginEnd to ensure
spacing from the button.

Comment on lines +45 to +55
<TextView
android:id="@+id/tvDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="[차단한 일자 (0000년 00월 00일)]"
android:textSize="12sp"
android:textColor="@color/black"
app:layout_constraintTop_toBottomOf="@id/tvReason"
app:layout_constraintStart_toStartOf="@id/tvReason"
app:layout_constraintEnd_toEndOf="@id/tvReason"
android:layout_marginTop="2dp" />
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

tvDate도 버튼과 겹치지 않도록 제약 정렬

tvDate가 tvReason의 End에 의존하고 있어(현재 구현상 문제는 없더라도) 버튼과의 독립적인 제약이 더 안전합니다. tvNickname 시작/btnMore 시작 사이로 고정하는 것을 권장합니다.

     <TextView
         android:id="@+id/tvDate"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:text="[차단한 일자 (0000년 00월 00일)]"
         android:textSize="12sp"
-        android:textColor="@color/black"
+        android:textColor="?attr/colorOnSurface"
         app:layout_constraintTop_toBottomOf="@id/tvReason"
-        app:layout_constraintStart_toStartOf="@id/tvReason"
-        app:layout_constraintEnd_toEndOf="@id/tvReason"
+        app:layout_constraintStart_toStartOf="@id/tvNickname"
+        app:layout_constraintEnd_toStartOf="@id/btnMore"
         android:layout_marginTop="2dp" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<TextView
android:id="@+id/tvDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="[차단한 일자 (0000년 00월 00일)]"
android:textSize="12sp"
android:textColor="@color/black"
app:layout_constraintTop_toBottomOf="@id/tvReason"
app:layout_constraintStart_toStartOf="@id/tvReason"
app:layout_constraintEnd_toEndOf="@id/tvReason"
android:layout_marginTop="2dp" />
<TextView
android:id="@+id/tvDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="[차단한 일자 (0000년 00월 00월 00일)]"
android:textSize="12sp"
android:textColor="?attr/colorOnSurface"
app:layout_constraintTop_toBottomOf="@id/tvReason"
app:layout_constraintStart_toStartOf="@id/tvNickname"
app:layout_constraintEnd_toStartOf="@id/btnMore"
android:layout_marginTop="2dp" />
🤖 Prompt for AI Agents
In app/src/main/res/layout/item_blocked_user.xml around lines 45 to 55, tvDate
currently constrains its start and end to tvReason; change its constraints so it
is independently positioned between tvNickname and btnMore by setting
app:layout_constraintStart_toStartOf="@id/tvNickname" and
app:layout_constraintEnd_toStartOf="@id/btnMore", remove the existing
constraints to tvReason, and keep the vertical/top constraint and margin as-is.

Comment on lines +58 to +68
<ImageButton
android:id="@+id/btnMore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="더보기"
android:src="@drawable/ic_more_vert_black_24dp"
app:tint="@color/black"
app:layout_constraintTop_toTopOf="@id/tvNickname"
app:layout_constraintBottom_toBottomOf="@id/tvNickname"
app:layout_constraintEnd_toEndOf="parent" />
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

더보기 버튼 접근성/테마 대응: 문자열 리소스, 터치 타겟, 틴트 속성

  • contentDescription은 하드코드 문자열 대신 string 리소스를 사용하세요.
  • 최소 터치 타겟(48dp) 확보가 필요합니다.
  • 틴트는 하드코드 색상 대신 테마 속성 사용을 권장합니다.
     <ImageButton
         android:id="@+id/btnMore"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:background="?attr/selectableItemBackgroundBorderless"
-        android:contentDescription="더보기"
+        android:contentDescription="@string/more"
         android:src="@drawable/ic_more_vert_black_24dp"
-        app:tint="@color/black"
+        app:tint="?attr/colorOnSurface"
+        android:minWidth="48dp"
+        android:minHeight="48dp"
         app:layout_constraintTop_toTopOf="@id/tvNickname"
         app:layout_constraintBottom_toBottomOf="@id/tvNickname"
-        app:layout_constraintEnd_toEndOf="parent" />
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginEnd="8dp" />

strings.xml에 다음 리소스 추가가 필요합니다:

<string name="more">더보기</string>
🤖 Prompt for AI Agents
In app/src/main/res/layout/item_blocked_user.xml around lines 58-68, the
ImageButton currently uses a hardcoded contentDescription, lacks a guaranteed
48dp touch target, and hardcodes tint color; replace the literal
contentDescription with @string/more (add <string name="more">더보기</string> to
strings.xml), ensure the button meets the 48dp minimum touch target by adding
appropriate minWidth/minHeight or padding (and adjust layout constraints if
needed) so the visual icon can remain wrap_content while the hit area is >=48dp,
and switch the tint to use a theme attribute (e.g. ?attr/colorOnSurface or
?attr/colorControlNormal) instead of a hardcoded color to support theming.

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)
app/src/main/res/layout/dialog_opinion.xml (2)

2-2: 빈 레이아웃에 ConstraintLayout은 과합니다 — FrameLayout/merge로 단순화 고려

자식 뷰가 없는 상태에서 ConstraintLayout은 비용만 증가시킵니다. 추후에도 제약이 필요 없다면 FrameLayout(또는 include 용도라면 )로 단순화하는 편이 좋습니다.

-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             android:layout_width="match_parent"
-             android:layout_height="match_parent">
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="match_parent"
+             android:layout_height="wrap_content"
+             android:padding="24dp">
 ...
-</androidx.constraintlayout.widget.ConstraintLayout>
+</FrameLayout>

Also applies to: 6-6


1-6: 파일 말미 개행 누락

파일 끝 개행이 없어 일부 Lint/리뷰 도구에서 경고가 날 수 있습니다. 개행 추가를 권장합니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e364f12 and a61d912.

📒 Files selected for processing (1)
  • app/src/main/res/layout/dialog_opinion.xml (1 hunks)

Comment on lines 2 to 4
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

다이얼로그 높이는 wrap_content 권장 + 기본 패딩과 ID 추가

현재 높이가 match_parent로 지정되어 있어 다이얼로그가 불필요하게 풀스크린처럼 보일 수 있습니다. 일반적인 커스텀 다이얼로그는 높이를 wrap_content로 두고, 적절한 패딩을 주는 것이 바람직합니다. 미리보기와 바인딩 편의를 위해 tools/app 네임스페이스 및 루트 ID도 함께 제안합니다.

-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             android:layout_width="match_parent"
-             android:layout_height="match_parent">
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             xmlns:app="http://schemas.android.com/apk/res-auto"
+             xmlns:tools="http://schemas.android.com/tools"
+             android:id="@+id/dialogOpinionRoot"
+             android:layout_width="match_parent"
+             android:layout_height="wrap_content"
+             android:padding="24dp"
+             tools:layout_width="320dp">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/dialogOpinionRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="24dp"
tools:layout_width="320dp">
🤖 Prompt for AI Agents
In app/src/main/res/layout/dialog_opinion.xml around lines 2 to 4 the root
ConstraintLayout uses android:layout_height="match_parent"; change it to
android:layout_height="wrap_content", add appropriate android:padding (e.g.,
16dp or theme spacing) on the root, add an id like
android:id="@+id/dialog_opinion_root", and include the tools namespace
declaration (xmlns:tools="http://schemas.android.com/tools") so previews and
binding helpers work correctly.

약간씩 수정이 되었는데, 올라온 내용 받아와야해서 먼저 커밋 날려놓겠습니다.
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

♻️ Duplicate comments (6)
app/src/main/java/umc/OnAirMate/ui/pop_up/ChangeNicknamePopup.kt (4)

1-1: 패키지명과 디렉터리 대소문자 불일치: 케이스-센시티브 환경에서 빌드 실패 위험

파일 경로는 .../umc/OnAirMate/...인데 패키지 선언은 umc.onairmate로 소문자입니다. 케이스-센시티브 파일시스템(예: CI Linux)에서 클래스를 찾지 못해 빌드/런타임 오류가 납니다. 디렉터리를 umc/onairmate로 통일해주세요.

다음 스크립트로 대소문자 혼용 디렉터리/파일을 점검하세요.

#!/bin/bash
echo "Uppercase 'OnAirMate' directories:"
fd -t d 'OnAirMate'

echo
echo "Lowercase 'onairmate' directories:"
fd -t d 'onairmate'

echo
echo "Kotlin files under both (to spot duplicates):"
fd -t f '.kt' OnAirMate
fd -t f '.kt' onairmate

28-30: onCheckNickname 콜백이 null일 때의 UX/상태 동기화 보완

콜백이 미제공(null)인 경우 버튼이 활성화되어도 클릭 시 아무 동작도 하지 않아 UX가 혼란스럽습니다. 콜백 할당 시점에 버튼 상태를 재계산하도록 커스텀 setter를 추가하세요.

적용 diff:

-    var onCheckNickname: ((String, (Boolean) -> Unit) -> Unit)? = null
+    var onCheckNickname: ((String, (Boolean) -> Unit) -> Unit)? = null
+        set(value) {
+            field = value
+            _binding?.let { b ->
+                val input = b.editNickname.text?.toString()?.trim().orEmpty()
+                val canCheck = input.length in 3..10 && field != null
+                b.checkNickname.isEnabled = canCheck
+                b.checkNickname.setBackgroundResource(
+                    if (canCheck) R.drawable.bg_btn_main else R.drawable.bg_btn_disabled
+                )
+            }
+        }

46-60: TextWatcher 빈 메서드 제거 + 버튼 활성화 조건에 콜백 존재 여부/trim 반영

  • detekt가 지적한 빈 블록(beforeTextChanged/onTextChanged)을 제거하세요.
  • 공백만 입력한 경우를 막기 위해 trim() 후 길이를 검사하세요.
  • onCheckNickname이 null이면 버튼을 비활성화하세요.

적용 diff(빈 메서드 제거 및 조건 보강):

-        binding.editNickname.addTextChangedListener(object : TextWatcher {
-            override fun afterTextChanged(s: Editable?) {
-                val input = s.toString()
-                val isValid = input.length in 3..10
-
-                binding.checkNickname.setBackgroundResource(
-                    if (isValid) R.drawable.bg_btn_main
-                    else R.drawable.bg_btn_disabled
-                )
-                binding.checkNickname.isEnabled = isValid
-            }
-
-            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
-            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
-        })
+        binding.editNickname.doAfterTextChanged { s ->
+            val input = s?.toString()?.trim().orEmpty()
+            val canCheck = input.length in 3..10 && onCheckNickname != null
+            binding.checkNickname.setBackgroundResource(
+                if (canCheck) R.drawable.bg_btn_main else R.drawable.bg_btn_disabled
+            )
+            binding.checkNickname.isEnabled = canCheck
+        }

추가(선택) import:

import androidx.core.widget.doAfterTextChanged

초기 진입 시 버튼 비활성화를 보장하고 싶다면 onViewCreated 초반에 다음을 추가하세요:

binding.checkNickname.isEnabled = false
binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)

62-81: 비동기 콜백 내 UI 업데이트의 생명주기/스레드 안전성 보장 + null 콜백 처리 + 중복 클릭 방지

  • 콜백이 백그라운드 스레드에서 호출될 수 있고 onDestroyView 이후라면 NPE 위험 있습니다. 메인 스레드 전환 및 _binding null 가드를 추가하세요.
  • 콜백이 null인 경우 사용자에게 안내 후 리턴하세요.
  • 요청 진행 중 버튼 비활성화로 중복 클릭을 방지하세요.

적용 diff:

-        binding.checkNickname.setOnClickListener {
-            val nickname = binding.editNickname.text.toString()
-
-            if (nickname.length !in 3..10) {
-                Toast.makeText(requireContext(), "닉네임은 3자 이상 10자 이하여야 합니다.", Toast.LENGTH_SHORT).show()
-                return@setOnClickListener
-            }
-
-            // 외부 콜백 호출해서 중복 검사 요청
-            onCheckNickname?.invoke(nickname) { isDuplicated ->
-                if (isDuplicated) {
-                    Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show()
-                    binding.editNickname.text?.clear()
-                    binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
-                    binding.checkNickname.isEnabled = false
-                } else {
-                    Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show()
-                }
-            }
+        binding.checkNickname.setOnClickListener {
+            val nickname = binding.editNickname.text?.toString()?.trim().orEmpty()
+
+            if (nickname.length !in 3..10) {
+                Toast.makeText(requireContext(), "닉네임은 3자 이상 10자 이하여야 합니다.", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+
+            val checker = onCheckNickname
+            if (checker == null) {
+                Toast.makeText(requireContext(), "중복 확인을 사용할 수 없습니다. 잠시 후 다시 시도해 주세요.", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+
+            // 진행 중 상태: 중복 클릭 방지
+            binding.checkNickname.isEnabled = false
+            binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
+
+            // 외부 콜백 호출해서 중복 검사 요청
+            checker.invoke(nickname) { isDuplicated ->
+                viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
+                    val b = _binding ?: return@launch
+                    if (isDuplicated) {
+                        Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show()
+                        // 필요 시 입력을 지우는 대신 에러 표시만 고려
+                        b.checkNickname.isEnabled = false
+                        b.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
+                    } else {
+                        Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show()
+                        b.checkNickname.isEnabled = true
+                        b.checkNickname.setBackgroundResource(R.drawable.bg_btn_main)
+                    }
+                }
+            }
app/src/main/res/layout/dialog_opinion.xml (1)

3-12: 다이얼로그 루트 고정 크기(dp) 사용 지양 — wrap_content/가변 폭 + RTL 대응 패딩으로 전환 권장

현재 320dp/300dp 고정 크기와 paddingLeft/paddingRight를 사용하고 있어 다양한 화면/폰트 환경에서 레이아웃 왜곡 가능성이 큽니다. 루트는 가급적 높이 wrap_content, 폭 match_parent(+ 다이얼로그 폭은 호스트가 결정)로 두고, 패딩은 start/end로 교체하세요. 이전 코멘트에서도 동일 사항을 권고드렸습니다.

-    android:layout_width="320dp"
-    android:layout_height="300dp"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
@@
-    android:paddingLeft="21dp"
-    android:paddingRight="21dp"
+    android:paddingStart="21dp"
+    android:paddingEnd="21dp"
app/src/main/java/umc/onairmate/ui/profile/ProfileFragment.kt (1)

55-68: 닉네임 저장 완료 후 UI/상태 동기화 누락 — 저장 콜백 연결 필요

팝업 닫힘 시점에 프로필의 닉네임 텍스트와 SharedPreferences를 업데이트해야 합니다. 이전 리뷰에서도 동일 지적이 있었습니다.

         binding.layoutNickname.setOnClickListener {
             val popup = ChangeNicknamePopup()

             // 팝업에 중복 검사 콜백 연결
             popup.onCheckNickname = { newNickname, callback ->
                 // 뷰모델의 checkNickname 함수 사용
                 changeNicknameViewModel.checkNickname(newNickname) { isDuplicated ->
                     // 결과 콜백 호출
                     callback(isDuplicated)
                 }
             }
+
+            // 닉네임 저장 완료 콜백: UI/로컬 상태 동기화
+            popup.onNicknameSaved = { newNick ->
+                binding.tvNicknameValue.text = newNick
+                nickname = newNick
+                requireContext()
+                    .getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
+                    .edit()
+                    .putString("nickname", newNick)
+                    .apply()
+            }

             popup.show(childFragmentManager, "ChangeNicknamePopup")
         }

참고: ChangeNicknamePopup에 var onNicknameSaved: ((String) -> Unit)? 정의 및 저장 성공 시점 호출이 필요합니다.

🧹 Nitpick comments (6)
app/src/main/java/umc/OnAirMate/ui/pop_up/ChangeNicknamePopup.kt (2)

62-79: 하드코딩 문자열의 리소스화(i18n/재사용성)

Toast 메시지를 string 리소스로 이동해 i18n·일관성·테스트 용이성을 높이세요.

예시:

  • strings.xml
    • nickname_length_invalid
    • nickname_check_unavailable
    • nickname_already_used
    • nickname_available

사용 예:

Toast.makeText(requireContext(), getString(R.string.nickname_length_invalid), Toast.LENGTH_SHORT).show()

82-113: 대량의 주석 처리된 이전 코드 정리

주석 블록(비사용 코드)이 길어 가독성을 저해합니다. 히스토리는 VCS에 남으니 제거를 권장합니다.

적용 diff(주석 블록 제거):

-            /*
-            viewLifecycleOwner.lifecycleScope.launch {
-                val isDuplicated = withContext(Dispatchers.IO) {
-                    repository.isNicknameDuplicated(nickname)
-                }
-
-                if (isDuplicated) {
-                    Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show()
-                    binding.editNickname.text.clear()
-                    binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
-                    binding.checkNickname.isEnabled = false
-                } else {
-                    Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show()
-                }
-            }
-            */
-
-//            viewLifecycleOwner.lifecycleScope.launch {
-//                val isDuplicated = withContext(Dispatchers.IO) {
-//                    repository.isNicknameDuplicated(nickname)
-//                }
-//
-//
-//                if (isDuplicated) {
-//                    Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show()
-//                    binding.editNickname.text.clear()
-//                    binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
-//                    binding.checkNickname.isEnabled = false
-//                } else {
-//                    Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show()
-//                }
-//            }
app/src/main/res/layout/dialog_opinion.xml (2)

51-60: Material 디자인 컴포넌트 사용 권장

Button 대신 MaterialButton을 사용하면 테마/상태/리플 등 일관된 머티리얼 UX를 얻을 수 있습니다. 기존 backgroundTint 커스터마이징도 테마 상속으로 대체가 용이합니다.

-    <Button
+    <com.google.android.material.button.MaterialButton
         android:id="@+id/btnSubmit"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
-        android:backgroundTint="@color/main"
+        style="@style/Widget.MaterialComponents.Button"
+        android:insetLeft="0dp"
+        android:insetRight="0dp"
+        android:insetTop="0dp"
+        android:insetBottom="0dp"
         android:text="@string/common_done"
         android:textSize="12sp"
         android:fontFamily="@font/pretendard_semi_bold"
         android:textColor="@android:color/white" />

27-34: 닫기 아이콘 접근성 보완: 포커스/클릭 속성 명시

터치 영역임을 명확히 하기 위해 clickable/focusable을 명시해 보세요. TalkBack 사용성에 도움이 됩니다.

     <ImageView
         android:id="@+id/btnClose"
         android:layout_width="12dp"
         android:layout_height="12dp"
         android:layout_alignParentEnd="true"
         android:src="@drawable/btn_close"
-        android:contentDescription="@string/common_close" />
+        android:contentDescription="@string/common_close"
+        android:clickable="true"
+        android:focusable="true" />
app/src/main/java/umc/onairmate/ui/profile/ProfileFragment.kt (2)

13-13: 불필요/오인가능한 import 제거

ContentProviderCompat.requireContext 정적 import는 Fragment의 requireContext()와 혼동을 유발합니다. 사용하지 않으므로 제거하세요.

-import androidx.core.content.ContentProviderCompat.requireContext

89-92: AlertDialog 대신 MaterialAlertDialogBuilder 사용 권장

머티리얼 테마 일관성과 접근성(색상/리플/패딩)을 위해 MaterialAlertDialogBuilder로 전환하세요.

-        val dialog = AlertDialog.Builder(requireContext())
+        val dialog = com.google.android.material.dialog.MaterialAlertDialogBuilder(requireContext())
             .setView(dialogView)
             .create()

추가: 상단 import에 아래를 추가하고, 기존 androidx.appcompat.app.AlertDialog는 제거하세요.

import com.google.android.material.dialog.MaterialAlertDialogBuilder
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a61d912 and 533b736.

📒 Files selected for processing (3)
  • app/src/main/java/umc/OnAirMate/ui/pop_up/ChangeNicknamePopup.kt (1 hunks)
  • app/src/main/java/umc/onairmate/ui/profile/ProfileFragment.kt (3 hunks)
  • app/src/main/res/layout/dialog_opinion.xml (1 hunks)
🧰 Additional context used
🪛 detekt (1.23.8)
app/src/main/java/umc/OnAirMate/ui/pop_up/ChangeNicknamePopup.kt

[warning] 58-58: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)


[warning] 59-59: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)

🔇 Additional comments (1)
app/src/main/java/umc/onairmate/ui/profile/ProfileFragment.kt (1)

83-85: 차단 리스트 네비게이션 연결 LGTM

Profile → BlockList 네비게이션 액션 정상 연결되어 있습니다.

Comment on lines +99 to 108
btnClose.setOnClickListener { dialog.dismiss() }
btnSubmit.setOnClickListener {
val feedback = etFeedback.text.toString()
if (feedback.isNotBlank()) {
// 의견 전송 로직
dialog.dismiss()
} else {
Toast.makeText(requireContext(), "내용을 입력해주세요", Toast.LENGTH_SHORT).show()
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

의견 전송 로직/UX 보강: 공백 트림, 다중 클릭 방지, 비동기 전송 처리

전송 TODO만 있고 메인 스레드에서 즉시 dismiss 합니다. 최소한 트림 체크, 중복 클릭 방지, 비동기 처리(예: lifecycleScope)로 UX/안정성을 확보하세요.

-        btnSubmit.setOnClickListener {
-            val feedback = etFeedback.text.toString()
-            if (feedback.isNotBlank()) {
-                // 의견 전송 로직
-                dialog.dismiss()
-            } else {
-                Toast.makeText(requireContext(), "내용을 입력해주세요", Toast.LENGTH_SHORT).show()
-            }
-        }
+        btnSubmit.setOnClickListener {
+            val feedback = etFeedback.text.toString().trim()
+            if (feedback.isBlank()) {
+                Toast.makeText(requireContext(), "내용을 입력해주세요", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            // 다중 클릭 방지
+            btnSubmit.isEnabled = false
+            viewLifecycleOwner.lifecycleScope.launch {
+                try {
+                    // TODO: 비동기 전송 로직 (예: repository.sendFeedback(feedback))
+                    Toast.makeText(requireContext(), "의견이 전송되었습니다.", Toast.LENGTH_SHORT).show()
+                    dialog.dismiss()
+                } catch (e: Exception) {
+                    Toast.makeText(requireContext(), "전송에 실패했습니다. 잠시 후 다시 시도해주세요.", Toast.LENGTH_SHORT).show()
+                } finally {
+                    btnSubmit.isEnabled = true
+                }
+            }
+        }

원하시면 Repository/Service 인터페이스 초안(전송 API/이메일 딥링크 등)까지 생성해 드리겠습니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
btnClose.setOnClickListener { dialog.dismiss() }
btnSubmit.setOnClickListener {
val feedback = etFeedback.text.toString()
if (feedback.isNotBlank()) {
// 의견 전송 로직
dialog.dismiss()
} else {
Toast.makeText(requireContext(), "내용을 입력해주세요", Toast.LENGTH_SHORT).show()
}
}
btnClose.setOnClickListener { dialog.dismiss() }
btnSubmit.setOnClickListener {
val feedback = etFeedback.text.toString().trim()
if (feedback.isBlank()) {
Toast.makeText(requireContext(), "내용을 입력해주세요", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
// 다중 클릭 방지
btnSubmit.isEnabled = false
viewLifecycleOwner.lifecycleScope.launch {
try {
// TODO: 비동기 전송 로직 (예: repository.sendFeedback(feedback))
Toast.makeText(requireContext(), "의견이 전송되었습니다.", Toast.LENGTH_SHORT).show()
dialog.dismiss()
} catch (e: Exception) {
Toast.makeText(requireContext(), "전송에 실패했습니다. 잠시 후 다시 시도해주세요.", Toast.LENGTH_SHORT).show()
} finally {
btnSubmit.isEnabled = true
}
}
}

android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="의견 보내기"
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

하드코딩 문자열 → string 리소스로 이동

다국어·접근성·일관성 측면에서 하드코딩 문자열은 지양하세요. strings.xml로 이관 바랍니다.

-            android:text="의견 보내기"
+            android:text="@string/opinion_title"
@@
-            android:contentDescription="닫기" />
+            android:contentDescription="@string/common_close" />
@@
-        android:hint="ON AIR mate에 대한 의견을 자유롭게 작성해주세요. mate들의 소중한 의견은 ON AIR mate에게 아주 큰 도움이 됩니다."
+        android:hint="@string/opinion_hint"
@@
-        android:text="완료"
+        android:text="@string/common_done"

추가: res/values/strings.xml

<resources>
    <string name="opinion_title">의견 보내기</string>
    <string name="opinion_hint">ON AIR mate에 대한 의견을 자유롭게 작성해주세요. mate들의 소중한 의견은 ON AIR mate에게 아주 큰 도움이 됩니다.</string>
    <string name="common_close">닫기</string>
    <string name="common_done">완료</string>
</resources>

Also applies to: 33-33, 44-44, 57-57

🤖 Prompt for AI Agents
app/src/main/res/layout/dialog_opinion.xml around line 23 (also apply same
changes at lines 33, 44, 57): the layout contains hardcoded Korean strings; move
these to res/values/strings.xml and replace the android:text and any hint/button
text attributes with references like @string/opinion_title,
@string/opinion_hint, @string/common_close, and @string/common_done. Update the
layout attributes to use @string/... instead of literal text and add the
provided string entries into res/values/strings.xml.

Comment on lines +39 to +48
android:layout_width="278.47dp"
android:layout_height="200dp"
android:layout_marginTop="13dp"
android:background="@drawable/background_input_text"
android:gravity="top"
android:hint="ON AIR mate에 대한 의견을 자유롭게 작성해주세요. mate들의 소중한 의견은 ON AIR mate에게 아주 큰 도움이 됩니다."
android:textSize="14sp"
android:fontFamily="@font/pretendard_regular"
android:inputType="textMultiLine"
android:padding="10dp" />
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

EditText 가로·세로 고정(dp) 및 줄 지정 누락 — match_parent/가변 높이 + minLines/스크롤 권장

278.47dp/200dp 고정 크기는 반응형 레이아웃을 저해합니다. 텍스트 길이에 맞춰 확대되도록 하고, 최소 줄 수와 스크롤을 지정해 입력 UX를 개선하세요.

-    android:layout_width="278.47dp"
-    android:layout_height="200dp"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
     android:layout_marginTop="13dp"
@@
-    android:gravity="top"
+    android:gravity="top|start"
@@
-    android:inputType="textMultiLine"
-    android:padding="10dp" />
+    android:inputType="textMultiLine"
+    android:minLines="5"
+    android:scrollbars="vertical"
+    android:padding="10dp" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
android:layout_width="278.47dp"
android:layout_height="200dp"
android:layout_marginTop="13dp"
android:background="@drawable/background_input_text"
android:gravity="top"
android:hint="ON AIR mate에 대한 의견을 자유롭게 작성해주세요. mate들의 소중한 의견은 ON AIR mate에게 아주 큰 도움이 됩니다."
android:textSize="14sp"
android:fontFamily="@font/pretendard_regular"
android:inputType="textMultiLine"
android:padding="10dp" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="13dp"
android:background="@drawable/background_input_text"
android:gravity="top|start"
android:hint="ON AIR mate에 대한 의견을 자유롭게 작성해주세요. mate들의 소중한 의견은 ON AIR mate에게 아주 큰 도움이 됩니다."
android:textSize="14sp"
android:fontFamily="@font/pretendard_regular"
android:inputType="textMultiLine"
android:minLines="5"
android:scrollbars="vertical"
android:padding="10dp" />
🤖 Prompt for AI Agents
In app/src/main/res/layout/dialog_opinion.xml around lines 39-48, the EditText
uses fixed dp width/height and lacks line/scroll settings; replace the fixed
sizes with responsive attributes (e.g., layout_width="match_parent" and
layout_height="wrap_content" or use constrained / match constraints in
ConstraintLayout), add a minimum visible line count (android:minLines="3" or
desired value), optionally limit growth with android:maxLines if needed, and
enable vertical scrolling by adding android:scrollbars="vertical" and
android:verticalScrollBarEnabled="true" (keep android:inputType="textMultiLine"
and android:gravity="top"); ensure scrolling works by disabling horizontal
scrolling (android:scrollHorizontally="false") so long input is scrollable and
the view remains responsive.

온보딩-프로필신고및차단 merge 하겠습니다. 문제 생길 시 수정하겠습니다.
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: 8

🔭 Outside diff range comments (1)
app/src/main/java/umc/onairmate/module/RepositoryModule.kt (1)

13-27: 패키지 디렉터리 대소문자 일관성 유지 필요

Linux/CI 환경에서는 umc/OnAirMateumc/onairmate 두 개의 디렉터리가 공존하면서 패키지 경로 불일치로 인한 빌드 오류(Gradle 의존성 해석 실패 등)를 일으킬 수 있습니다. 코드 내 패키지 선언은 모두 umc.onairmate.*를 사용하고 있으므로, 디렉터리명을 전부 소문자(onairmate)로 통일해주세요.

• 수정 대상
app/src/main/java/umc/OnAirMate/app/src/main/java/umc/onairmate/
• 필요 시 IDE나 Gradle 캐시 정리 후 경로 재인식 확인

🧹 Nitpick comments (2)
app/src/main/java/umc/onairmate/module/RepositoryModule.kt (1)

58-69: Repository 스코프(ViewModelScoped) 재검토 권장.

Repository는 네트워크/DB 캐시를 공유하거나 비용 큰 리소스를 재사용하는 경우가 많아, 보통 @singleton + SingletonComponent로 관리합니다. 현재 ViewModelScoped로 제공하면 ViewModel마다 인스턴스가 생성되어 캐시/상태 공유가 어려울 수 있습니다. 의도적으로 ViewModel 단위 격리를 원한 것이 아니라면 Singleton 전환을 고려해주세요.

예시(별도 모듈로 분리하는 방안):

@Module
@InstallIn(SingletonComponent::class)
object RepositorySingletonModule {
  @Singleton @Provides
  fun providesNicknameRepository(nicknameService: NicknameService): NicknameRepository =
    NicknameRepositoryImpl(nicknameService)

  @Singleton @Provides
  fun providesJoinRepository(joinService: JoinService): JoinRepository =
    JoinRepositoryImpl(joinService)
}
app/src/main/java/umc/onairmate/ui/pop_up/TwoButtonPopup.kt (1)

56-56: 빈 함수 구현체 정리 필요

rightClickFunction()의 빈 구현체는 "아니오" 버튼 클릭 시 아무 동작도 하지 않는다는 의도를 명확히 보여주지만, 코드 분석 도구에서 경고를 발생시킵니다. 명시적인 주석을 추가하는 것이 좋습니다.

-                override fun rightClickFunction() {}
+                override fun rightClickFunction() {
+                    // "아니오" 선택 시 별도 동작 없음
+                }

Also applies to: 66-66

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 533b736 and 09ec861.

📒 Files selected for processing (10)
  • app/src/main/java/umc/OnAirMate/data/repository/repository/JoinRepository.kt (1 hunks)
  • app/src/main/java/umc/onairmate/data/repository/repository/FriendRepository.kt (1 hunks)
  • app/src/main/java/umc/onairmate/module/RepositoryModule.kt (3 hunks)
  • app/src/main/java/umc/onairmate/module/ServiceModule.kt (2 hunks)
  • app/src/main/java/umc/onairmate/ui/friend/FriendViewModel.kt (2 hunks)
  • app/src/main/java/umc/onairmate/ui/friend/list/FriendListRVAdapter.kt (3 hunks)
  • app/src/main/java/umc/onairmate/ui/friend/list/FriendListTabFragment.kt (5 hunks)
  • app/src/main/java/umc/onairmate/ui/pop_up/TwoButtonPopup.kt (1 hunks)
  • app/src/main/res/navigation/mobile_navigation.xml (1 hunks)
  • app/src/main/res/navigation/nav_graph.xml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/java/umc/onairmate/module/ServiceModule.kt
  • app/src/main/res/navigation/nav_graph.xml
🧰 Additional context used
🪛 detekt (1.23.8)
app/src/main/java/umc/onairmate/ui/pop_up/TwoButtonPopup.kt

[warning] 56-56: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)


[warning] 66-66: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)

🔇 Additional comments (3)
app/src/main/java/umc/onairmate/module/RepositoryModule.kt (1)

15-15: 임포트 경로 문제 없음: 현재 import는 클래스의 패키지 선언과 일치합니다

NicknameRepository.kt의 선언부를 확인한 결과 패키지 경로가

package umc.onairmate.data.repository

로 되어 있어,

import umc.onairmate.data.repository.NicknameRepository

가 올바른 경로입니다.

만약 실제로 umc.onairmate.data.repository.repository 하위로 옮기길 원한다면,

  • 클래스 파일의 패키지 선언을 package umc.onairmate.data.repository.repository로 변경
  • 파일 디렉터리 구조를 이에 맞춰 이동

해야 하지만, 현재 상태에서는 import 수정이 필요하지 않습니다.

Likely an incorrect or invalid review comment.

app/src/main/java/umc/onairmate/ui/pop_up/TwoButtonPopup.kt (1)

50-70: Companion object의 helper 메서드들이 유용합니다!

차단과 신고를 위한 정적 헬퍼 메서드를 추가한 것은 코드 재사용성을 높이는 좋은 접근입니다. 팝업 생성 로직이 중앙화되어 일관성 있는 UI를 제공할 수 있습니다.

app/src/main/java/umc/onairmate/data/repository/repository/FriendRepository.kt (1)

23-24: 새로운 API 메서드 추가 확인

차단과 신고 기능을 위한 새로운 suspend 함수들이 적절히 추가되었습니다. 인터페이스 설계가 일관성 있게 유지되고 있습니다.

suspend fun joinProfile(request: JoinProfileRequest): Response<JoinProfileResponse>
suspend fun checkNickname(nickname: String): Response<NicknameCheckResponse>
}
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

닉네임 중복확인 책임이 중복됩니다 (JoinRepository vs NicknameRepository).

이 PR에서 NicknameRepository가 도입되었는데, JoinRepository에 여전히 checkNickname이 남아 있습니다. 동일 기능이 두 레이어에 공존하면 호출 지점 혼란/중복 유지보수 위험이 큽니다. 닉네임 관련 책임은 NicknameRepository로 일원화하고, JoinRepository의 checkNickname은 제거(또는 @deprecated로 마이그레이션 경로 안내)하는 것을 권장합니다.

다음 스크립트로 실제 사용 지점을 확인해 정리 범위를 파악해 보세요.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "checkNickname 사용 위치:"
rg -n --hidden --glob '!*build*' 'checkNickname\(' -A 2

echo
echo "NicknameRepository 사용 위치:"
rg -n --hidden --glob '!*build*' 'NicknameRepository' -A 2

Length of output: 7245


닉네임 중복 검사 책임 통합 필요
JoinRepository에 남아 있는 checkNickname 메서드는 새로 도입된 NicknameRepository에 이관된 기능이므로, 아래 항목을 반영해주세요.

  • app/src/main/java/umc/OnAirMate/data/repository/repository/JoinRepository.kt
    suspend fun checkNickname(nickname: String): Response<NicknameCheckResponse> 제거 또는 @Deprecated 처리
  • app/src/main/java/umc/OnAirMate/data/repository/repositoryImpl/JoinRepositoryImpl.kt
    override suspend fun checkNickname(…) 및 관련 구현 삭제
  • app/src/main/java/umc/OnAirMate/ui/join/JoinViewModel.kt
    joinRepository.checkNickname 호출부를 NicknameRepository(isNicknameDuplicated)로 교체
    • Hilt 모듈에 NicknameRepository 주입 설정(이미 제공 중이라면 ViewModel 생성자에 추가)

위 변경을 통해 닉네임 중복 검사 책임을 NicknameRepository로 일원화하고, 중복 유지보수 리스크를 제거해주세요.

🤖 Prompt for AI Agents
In app/src/main/java/umc/OnAirMate/data/repository/repository/JoinRepository.kt
around line 12, remove or mark as @Deprecated the suspend fun
checkNickname(nickname: String): Response<NicknameCheckResponse> since
nickname-check responsibility has moved to NicknameRepository; in
app/src/main/java/umc/OnAirMate/data/repository/repositoryImpl/JoinRepositoryImpl.kt
remove the override suspend fun checkNickname(...) and its implementation; in
app/src/main/java/umc/OnAirMate/ui/join/JoinViewModel.kt replace any
joinRepository.checkNickname(...) calls with
NicknameRepository.isNicknameDuplicated(...) and ensure the ViewModel receives
NicknameRepository via Hilt (add injection to the constructor or configure the
Hilt module if not already provided).

Comment on lines 53 to 61
fun blockFriend(userId: Int) {
// repository를 통해 서버 요청
viewModelScope.launch {
val response = repository.blockFriend(userId)
if (response.isSuccessful) {
result.value = "친구가 차단되었습니다."
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

blockFriend 메서드의 토큰 누락 및 에러 처리 부재

blockFriend 메서드에서 액세스 토큰을 전달하지 않고 있으며, 에러 처리가 없습니다. 다른 메서드들과 일관성을 유지하도록 수정이 필요합니다.

 fun blockFriend(userId: Int) {
-    // repository를 통해 서버 요청
     viewModelScope.launch {
-        val response = repository.blockFriend(userId)
-        if (response.isSuccessful) {
-            result.value = "친구가 차단되었습니다."
+        val token = getToken()
+        if (token == null) {
+            Log.e(TAG, "토큰이 없습니다")
+            return@launch
+        }
+        val response = repository.blockFriend(token, userId)
+        when (response) {
+            is DefaultResponse.Success -> {
+                Log.d(TAG, "blockFriend 성공")
+                _result.postValue("친구가 차단되었습니다.")
+            }
+            is DefaultResponse.Error -> {
+                Log.e(TAG, "에러: ${response.code} - ${response.message}")
+                _result.postValue(response.message)
+            }
         }
     }
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 63 to 70
fun reportFriend(userId: Int) {
viewModelScope.launch {
val response = repository.reportFriend(userId)
if (response.isSuccessful) {
result.value = "친구가 신고되었습니다."
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

reportFriend 메서드의 토큰 누락 및 에러 처리 부재

reportFriend 메서드도 동일하게 토큰 전달과 에러 처리가 누락되어 있습니다.

 fun reportFriend(userId: Int) {
     viewModelScope.launch {
-        val response = repository.reportFriend(userId)
-        if (response.isSuccessful) {
-            result.value = "친구가 신고되었습니다."
+        val token = getToken()
+        if (token == null) {
+            Log.e(TAG, "토큰이 없습니다")
+            return@launch
+        }
+        val response = repository.reportFriend(token, userId)
+        when (response) {
+            is DefaultResponse.Success -> {
+                Log.d(TAG, "reportFriend 성공")
+                _result.postValue("친구가 신고되었습니다.")
+            }
+            is DefaultResponse.Error -> {
+                Log.e(TAG, "에러: ${response.code} - ${response.message}")
+                _result.postValue(response.message)
+            }
         }
     }
 }
🤖 Prompt for AI Agents
In app/src/main/java/umc/onairmate/ui/friend/FriendViewModel.kt around lines 63
to 70, reportFriend currently calls repository.reportFriend(userId) without
supplying the auth token and lacks error handling; update the method to obtain
the current auth token (from the ViewModel state, preferences, or an injected
auth manager) and pass it into repository.reportFriend(token, userId), wrap the
network call in a try/catch inside viewModelScope.launch to handle exceptions,
and on failure (response.isSuccessful == false or caught exception) set an
appropriate error message on result or an error LiveData so callers can react.

Comment on lines 76 to 81
binding.btnMore.setOnClickListener {
// 더보기 메뉴를 팝업 등으로 처리
// 여기서는 예시로 차단/신고 버튼을 바로 실행
onBlockClick?.invoke(data)
onReportClick?.invoke(data)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

차단과 신고 동시 호출 문제

btnMore 클릭 시 onBlockClickonReportClick를 모두 호출하고 있습니다. 사용자가 선택할 수 있도록 팝업 메뉴나 다이얼로그를 표시해야 합니다.

 binding.btnMore.setOnClickListener {
-    // 더보기 메뉴를 팝업 등으로 처리
-    // 여기서는 예시로 차단/신고 버튼을 바로 실행
-    onBlockClick?.invoke(data)
-    onReportClick?.invoke(data)
+    // 더보기 메뉴 팝업 표시
+    showMoreOptionsDialog(data)
 }

별도로 showMoreOptionsDialog 메서드를 구현하여 사용자가 차단 또는 신고를 선택할 수 있도록 해야 합니다.

더보기 메뉴 다이얼로그 구현을 도와드릴까요?

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
binding.btnMore.setOnClickListener {
// 더보기 메뉴를 팝업 등으로 처리
// 여기서는 예시로 차단/신고 버튼을 바로 실행
onBlockClick?.invoke(data)
onReportClick?.invoke(data)
}
binding.btnMore.setOnClickListener {
// 더보기 메뉴 팝업 표시
showMoreOptionsDialog(data)
}

Comment on lines +44 to +48
tools:layout="@layout/fragment_profile">
<action
android:id="@+id/action_profileFragment_to_blockListFragment"
app:destination="@id/blockListFragment" />
</fragment>
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

프로필 → 차단 리스트 액션 추가는 적절합니다. 다만 ID 네이밍과 중복 여부 점검 바랍니다.

  • 구현 자체는 문제 없습니다. 다만 기존 목적지 ID들은 navigation_* 접두어를 사용하고 있는데, 새 목적지(fragment)는 blockListFragment로 일관성이 약간 깨집니다. 일관성 유지를 위해 @+id/navigation_block_list 형태로의 변경을 권장합니다.
  • 또, 동일한 액션 ID가 다른 내비게이션 그래프에 이미 존재하는지 확인해주세요. 리소스 병합 시 이름 충돌로 혼동이 생길 수 있습니다.

다음 스크립트로 액션 ID 중복 여부를 확인할 수 있습니다:


🏁 Script executed:

#!/bin/bash
# 액션 ID 중복 검색
rg -n 'action_profileFragment_to_blockListFragment'

# 목적지 ID 사용처 검색 (리네임 시 영향 범위 파악)
rg -n '@\+id/blockListFragment|@id/blockListFragment'

Length of output: 994


ID 네이밍 및 중복 충돌 해결 필요

스크립트 실행 결과, action 및 destination ID가 여러 곳에서 중복 정의되어 리소스 병합 시 충돌 가능성이 확인되었습니다. 다음 사항을 반영해 수정해주세요:

  • 중복 정의 현황

    • app/src/main/res/navigation/nav_graph.xml, mobile_navigation.xml:
      android:id="@+id/action_profileFragment_to_blockListFragment"
    • blockListFragment destination ID도 nav_graph.xml (59, 63), mobile_navigation.xml (47, 51), fragment_block_list.xml (7)에서 중복 정의
  • 제안하는 일관된 네이밍 (navigation_ 접두어 사용)

    • Action ID
      - @+id/action_profileFragment_to_blockListFragment
      + @+id/navigation_action_profile_to_block_list
    • Destination ID
      - @+id/blockListFragment
      + @+id/navigation_block_list
  • 변경 시 함께 업데이트할 참조

    • ProfileFragment.kt (84행)
      - findNavController().navigate(R.id.action_profileFragment_to_blockListFragment)
      + findNavController().navigate(R.id.navigation_action_profile_to_block_list)
    • nav_graph.xml, mobile_navigation.xml 내 app:destination 속성
🤖 Prompt for AI Agents
In app/src/main/res/navigation/mobile_navigation.xml around lines 44 to 48, the
action and destination IDs conflict with duplicates in nav_graph.xml and
fragment_block_list.xml; rename the action id to use a consistent navigation_
prefix (e.g. android:id="@+id/navigation_action_profile_to_blockList") and
rename the destination id for blockListFragment similarly (e.g.
android:id="@+id/navigation_blockListFragment"), then update all references:
change app:destination attributes in both nav_graph.xml and
mobile_navigation.xml to the new destination id and update the
ProfileFragment.kt reference at line 84 to use the new action id; ensure no
other XML or Kotlin files still reference the old IDs and run a resource build
to confirm no collisions remain.

Comment on lines +50 to +53
<fragment
android:id="@+id/blockListFragment"
android:name="umc.onairmate.ui.profile.BlockListFragment"
tools:layout="@layout/fragment_block_list" />
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

BlockListFragment에 android:label 추가 권장 (툴바 타이틀/접근성 개선).

툴바 타이틀과 접근성(탐색/스크린리더) 측면에서 label 정의가 필요합니다. 문자열 리소스를 추가하고 아래처럼 라벨을 지정하세요.

적용 diff:

-    <fragment
-        android:id="@+id/blockListFragment"
-        android:name="umc.onairmate.ui.profile.BlockListFragment"
-        tools:layout="@layout/fragment_block_list" />
+    <fragment
+        android:id="@+id/blockListFragment"
+        android:name="umc.onairmate.ui.profile.BlockListFragment"
+        android:label="@string/title_block_list"
+        tools:layout="@layout/fragment_block_list" />

strings.xml (별도 파일)에 다음 리소스를 추가해 주세요:

<string name="title_block_list">차단 목록</string>

추가 확인:

  • 프래그먼트 클래스 존재 및 패키지 경로가 일치하는지 확인이 필요합니다. 아래 스크립트로 검증할 수 있습니다.

🏁 Script executed:

#!/bin/bash
# BlockListFragment.kt 파일 위치 및 패키지 확인
fd -a BlockListFragment.kt
rg -n '^package .*BlockListFragment' -A 2 --glob '**/BlockListFragment.kt'

Length of output: 188


🏁 Script executed:

#!/bin/bash
# BlockListFragment.kt의 패키지 선언 확인
sed -n '1,5p' app/src/main/java/umc/OnAirMate/ui/profile/BlockListFragment.kt

Length of output: 195


BlockListFragment에 android:label 속성 추가 권장
툴바 타이틀과 스크린리더 접근성 개선을 위해 fragment에 label을 지정해주세요.

수정 위치

  • 파일: app/src/main/res/navigation/mobile_navigation.xml
  • 대상 fragment ID: @+id/blockListFragment (패키지 경로 umc.onairmate.ui.profile.BlockListFragment 확인 완료)

적용 예시 diff:

-    <fragment
-        android:id="@+id/blockListFragment"
-        android:name="umc.onairmate.ui.profile.BlockListFragment"
-        tools:layout="@layout/fragment_block_list" />
+    <fragment
+        android:id="@+id/blockListFragment"
+        android:name="umc.onairmate.ui.profile.BlockListFragment"
+        android:label="@string/title_block_list"
+        tools:layout="@layout/fragment_block_list" />

추가: res/values/strings.xml에 아래 리소스 추가

<string name="title_block_list">차단 목록</string>
🤖 Prompt for AI Agents
In app/src/main/res/navigation/mobile_navigation.xml around lines 50 to 53, the
fragment with id @+id/blockListFragment
(umc.onairmate.ui.profile.BlockListFragment) lacks an android:label which is
used for toolbar titles and accessibility; add
android:label="@string/title_block_list" to that fragment element, and add the
string resource title_block_list with value "차단 목록" to res/values/strings.xml.

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

🔭 Outside diff range comments (3)
app/src/main/java/umc/onairmate/ui/friend/FriendViewModel.kt (3)

76-99: getFriendList: 예외 처리 및 과도한 로그 출력 개선 필요.

  • 네트워크 예외 시 크래시 가능성이 있습니다. try/catch + finally로 방어하세요.
  • 전체 데이터 객체를 로그로 출력하는 것은 과도하며 PII/성능에 불리합니다. 개수만 로그로 남기세요.
-            val result = repository.getFriendList(token)
-            Log.d(TAG, "getFriendList api 호출")
-            when (result) {
-                is DefaultResponse.Success -> {
-                    Log.d(TAG, "응답 성공 : ${result.data}")
-                    _friendList.postValue(result.data)
-                }
-                is DefaultResponse.Error -> {
-                    Log.e(TAG, "에러: ${result.code} - ${result.message} ")
-                    _friendList.postValue(emptyList())
-                }
-            }
-            _isLoading.value = false
+            try {
+                val result = repository.getFriendList(token)
+                Log.d(TAG, "getFriendList api 호출")
+                when (result) {
+                    is DefaultResponse.Success -> {
+                        Log.d(TAG, "응답 성공: ${result.data.size}건")
+                        _friendList.postValue(result.data)
+                    }
+                    is DefaultResponse.Error -> {
+                        Log.e(TAG, "에러: ${result.code} - ${result.message}")
+                        _friendList.postValue(emptyList())
+                    }
+                }
+            } catch (e: Exception) {
+                Log.e(TAG, "getFriendList 예외", e)
+                _friendList.postValue(emptyList())
+            } finally {
+                _isLoading.value = false
+            }

101-124: getRequestedFriendList: 예외 처리 및 로그 개선 동일 적용 권장.

-            val result = repository.getRequestedFriendList(token)
-            Log.d(TAG, "getRequestedFriendList api 호출")
-            when (result) {
-                is DefaultResponse.Success -> {
-                    Log.d(TAG, "응답 성공 : ${result.data}")
-                    _requestedFriendList.postValue(result.data)
-                }
-                is DefaultResponse.Error -> {
-                    Log.e(TAG, "에러: ${result.code} - ${result.message} ")
-                    _requestedFriendList.postValue(emptyList())
-                }
-            }
-            _isLoading.value = false
+            try {
+                val result = repository.getRequestedFriendList(token)
+                Log.d(TAG, "getRequestedFriendList api 호출")
+                when (result) {
+                    is DefaultResponse.Success -> {
+                        Log.d(TAG, "응답 성공: ${result.data.size}건")
+                        _requestedFriendList.postValue(result.data)
+                    }
+                    is DefaultResponse.Error -> {
+                        Log.e(TAG, "에러: ${result.code} - ${result.message}")
+                        _requestedFriendList.postValue(emptyList())
+                    }
+                }
+            } catch (e: Exception) {
+                Log.e(TAG, "getRequestedFriendList 예외", e)
+                _requestedFriendList.postValue(emptyList())
+            } finally {
+                _isLoading.value = false
+            }

126-149: searchUser: 예외 처리 및 로그 개선 동일 적용 권장.

-            val result = repository.searchUser(token, nickname)
-            Log.d(TAG, "searchUser api 호출")
-            when (result) {
-                is DefaultResponse.Success -> {
-                    Log.d(TAG, "응답 성공 : ${result.data}")
-                    _searchedUserList.postValue(result.data)
-                }
-                is DefaultResponse.Error -> {
-                    Log.e(TAG, "에러: ${result.code} - ${result.message} ")
-                    _searchedUserList.postValue(emptyList())
-                }
-            }
-            _isLoading.value = false
+            try {
+                val result = repository.searchUser(token, nickname)
+                Log.d(TAG, "searchUser api 호출")
+                when (result) {
+                    is DefaultResponse.Success -> {
+                        Log.d(TAG, "응답 성공: ${result.data.size}건")
+                        _searchedUserList.postValue(result.data)
+                    }
+                    is DefaultResponse.Error -> {
+                        Log.e(TAG, "에러: ${result.code} - ${result.message}")
+                        _searchedUserList.postValue(emptyList())
+                    }
+                }
+            } catch (e: Exception) {
+                Log.e(TAG, "searchUser 예외", e)
+                _searchedUserList.postValue(emptyList())
+            } finally {
+                _isLoading.value = false
+            }
♻️ Duplicate comments (3)
app/src/main/java/umc/onairmate/ui/friend/FriendViewModel.kt (2)

54-61: blockFriend: 토큰 미전달 및 응답 처리 방식 불일치 (기존 코멘트 미반영).

  • 토큰을 전달하지 않습니다.
  • 다른 메서드에서 사용하는 DefaultResponse 패턴과 다르게 isSuccessful 체크를 사용합니다.
  • 예외 처리와 에러 메시지 전달이 없습니다.

아래와 같이 토큰 획득/검증, DefaultResponse 패턴, 예외 처리, 사용자 메시지를 통일해 주세요.

-    fun blockFriend(userId: Int) {
-        viewModelScope.launch {
-            val response = repository.blockFriend(userId)
-            if (response.isSuccessful) {
-                _result.value = "친구가 차단되었습니다."
-            }
-        }
-    }
+    fun blockFriend(userId: Int) {
+        viewModelScope.launch {
+            val token = getToken()
+            if (token == null) {
+                Log.e(TAG, "토큰이 없습니다")
+                _result.postValue("로그인이 필요합니다.")
+                return@launch
+            }
+            try {
+                when (val response = repository.blockFriend(token, userId)) {
+                    is DefaultResponse.Success -> {
+                        Log.d(TAG, "blockFriend 성공")
+                        _result.postValue(response.data.message ?: "친구가 차단되었습니다.")
+                    }
+                    is DefaultResponse.Error -> {
+                        Log.e(TAG, "blockFriend 실패: ${response.code} - ${response.message}")
+                        _result.postValue(response.message)
+                    }
+                }
+            } catch (e: Exception) {
+                Log.e(TAG, "blockFriend 예외", e)
+                _result.postValue("잠시 후 다시 시도해주세요.")
+            }
+        }
+    }

63-70: reportFriend: 토큰 미전달 및 응답 처리 방식 불일치 (기존 코멘트 미반영).

blockFriend와 동일한 이슈입니다. 아래처럼 정렬해 주세요.

-    fun reportFriend(userId: Int) {
-        viewModelScope.launch {
-            val response = repository.reportFriend(userId)
-            if (response.isSuccessful) {
-                _result.value = "친구가 신고되었습니다."
-            }
-        }
-    }
+    fun reportFriend(userId: Int) {
+        viewModelScope.launch {
+            val token = getToken()
+            if (token == null) {
+                Log.e(TAG, "토큰이 없습니다")
+                _result.postValue("로그인이 필요합니다.")
+                return@launch
+            }
+            try {
+                when (val response = repository.reportFriend(token, userId)) {
+                    is DefaultResponse.Success -> {
+                        Log.d(TAG, "reportFriend 성공")
+                        _result.postValue(response.data.message ?: "친구가 신고되었습니다.")
+                    }
+                    is DefaultResponse.Error -> {
+                        Log.e(TAG, "reportFriend 실패: ${response.code} - ${response.message}")
+                        _result.postValue(response.message)
+                    }
+                }
+            } catch (e: Exception) {
+                Log.e(TAG, "reportFriend 예외", e)
+                _result.postValue("잠시 후 다시 시도해주세요.")
+            }
+        }
+    }
app/src/main/java/umc/onairmate/ui/friend/list/FriendListTabFragment.kt (1)

103-127: 어댑터 생성 시 context 전달 누락 이슈가 해결되었습니다.

requireContext()와 itemClickListener를 명시적으로 전달하여 이전 문제를 해소했습니다.

🧹 Nitpick comments (8)
app/src/main/java/umc/onairmate/data/model/response/DefaultResponse.kt (1)

19-21: 클래스명 ‘Error’는 kotlin.Error와의 개념 충돌 가능성 있음

패키지 한정으로 사용하면 문제는 없지만, import 상황에 따라 혼동될 수 있습니다. Failure 또는 ApiError처럼 도메인 의미가 분명한 이름을 고려해 보세요.

app/src/main/java/umc/OnAirMate/ui/friend/list/FriendItemClickListener.kt (1)

7-16: 이벤트 메서드 네이밍 일관성 개선 제안

clickMessage, clickBlock 등과 onItemClick/onRequestItemClick가 혼재되어 있습니다. onClickXxx로 통일하거나, 제네릭 onItemClick 계열로만 정리하면 API가 더 명료해집니다.

app/src/main/java/umc/OnAirMate/ui/friend/FriendRVAdapterDiffCallback.kt (1)

7-18: Any 타입 사용 최소화 제안 — 공통 인터페이스/시드 클래스 도입 고려

DiffUtil.ItemCallback<Any> 대신 공통 인터페이스(예: interface ListItem { val userId: Int }) 또는 sealed 계층을 도입하면 타입 안정성과 가독성이 향상됩니다. 추후 아이템 타입이 늘어나도 when 분기 보일러플레이트를 줄일 수 있습니다.

app/src/main/java/umc/onairmate/ui/friend/FriendViewModel.kt (2)

151-174: 중복 패턴을 유틸로 추출해 가독성과 안전성을 높이세요.

토큰 검증/에러 처리/로딩 토글이 반복됩니다. ViewModel 내부에 withToken {}/safeApiCall {} 헬퍼를 만들어 중복을 제거하는 것을 권장합니다. 필요 시 보일러플레이트 제거용 코드 템플릿 제공 가능합니다.

Also applies to: 176-199, 201-224, 226-249


85-90: 민감/대용량 데이터 전체를 로그로 출력하지 마세요.

List 전체 객체를 로그로 남기는 것은 PII/성능 이슈 소지가 있습니다. 건수 등 최소 정보만 출력하도록 조정하세요.

Also applies to: 112-115, 139-140, 164-166, 189-191, 214-216, 239-241

app/src/main/java/umc/onairmate/ui/friend/list/FriendListRVAdapter.kt (2)

30-35: 알 수 없는 타입 예외 메시지를 구체화하세요.

디버깅 편의를 위해 실제 클래스명을 포함한 메시지가 좋습니다.

-            else -> throw IllegalArgumentException("Unknown type")
+            else -> throw IllegalArgumentException("Unknown item type: ${getItem(position)::class.java.name}")

53-57: onBindViewHolder에서 안전한 캐스팅 또는 when(getItemViewType) 기반 바인딩 권장.

현재 강제 캐스팅(as)은 런타임 예외 위험이 있습니다. getItemViewType(position)에 따라 분기하거나, 안전 캐스팅을 사용해 방어하세요.

-        when (holder) {
-            is FriendViewHolder -> holder.bind(getItem(position) as FriendData)
-            is FriendRequestViewHolder -> holder.bind(getItem(position) as RequestedFriendData)
-        }
+        when (holder) {
+            is FriendViewHolder -> (getItem(position) as? FriendData)?.let(holder::bind)
+            is FriendRequestViewHolder -> (getItem(position) as? RequestedFriendData)?.let(holder::bind)
+        }
app/src/main/java/umc/onairmate/ui/friend/list/FriendListTabFragment.kt (1)

80-92: 빈 상태 메시지/텍스트는 문자열 리소스로 관리하세요.

하드코딩된 문자열은 i18n과 유지보수에 불리합니다. strings.xml로 이동 후 getString(...)을 사용하세요.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 09ec861 and 3cb310e.

📒 Files selected for processing (8)
  • app/src/main/java/umc/OnAirMate/ui/friend/FriendRVAdapterDiffCallback.kt (1 hunks)
  • app/src/main/java/umc/OnAirMate/ui/friend/list/FriendItemClickListener.kt (1 hunks)
  • app/src/main/java/umc/onairmate/data/model/response/DefaultResponse.kt (1 hunks)
  • app/src/main/java/umc/onairmate/data/repository/repository/FriendRepository.kt (1 hunks)
  • app/src/main/java/umc/onairmate/data/repository/repositoryImpl/FriendRepositoryImpl.kt (1 hunks)
  • app/src/main/java/umc/onairmate/ui/friend/FriendViewModel.kt (11 hunks)
  • app/src/main/java/umc/onairmate/ui/friend/list/FriendListRVAdapter.kt (1 hunks)
  • app/src/main/java/umc/onairmate/ui/friend/list/FriendListTabFragment.kt (5 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/src/main/java/umc/onairmate/ui/friend/list/FriendListTabFragment.kt (1)
app/src/main/java/umc/OnAirMate/ui/chat_room/message/VideoChatFragment.kt (1)
  • initData (88-92)
🔇 Additional comments (6)
app/src/main/java/umc/onairmate/data/model/response/DefaultResponse.kt (1)

6-7: isSuccessful 플래그 도입 깔끔합니다 — 호출부 분기 단순화에 도움

Success/Error가 공통적으로 isSuccessful을 제공하도록 한 설계는 호출부 가독성과 처리 흐름을 확실히 개선합니다. 현재 구현 상태로 충분히 사용 가능합니다.

Also applies to: 12-13, 20-21

app/src/main/java/umc/OnAirMate/ui/friend/FriendRVAdapterDiffCallback.kt (1)

7-18: DiffUtil 콜백 구현 적절 — 타입별 식별자 비교로 안정적입니다

FriendData/RequestedFriendData를 타입 구분 후 userId로 식별하고, 컨텐츠 비교는 구조 동등성에 의존하는 접근이 합리적입니다.

app/src/main/java/umc/onairmate/ui/friend/FriendViewModel.kt (3)

30-41: 백킹 프로퍼티 패턴 적용은 적절합니다 (LiveData 노출 방식 LGTM).

내부 변경 가능성과 외부 불변성을 잘 분리했습니다.


42-49: 로딩 상태와 결과 메시지의 분리 관리가 명확합니다.

UI 반응성 측면에서 상태 관리가 명료합니다.


72-74: 결과 초기화 메서드 명확합니다.

UI에서 토스트/스낵바 1회 노출 후 상태 리셋 용도로 적절합니다.

app/src/main/java/umc/onairmate/ui/friend/list/FriendListTabFragment.kt (1)

165-171: 팝업 헬퍼는 좋습니다. dialog.dismiss 제어만 확인하세요.

TwoButtonPopup이 내부에서 닫힘을 관리한다면 ok, 아니라면 클릭 후 닫힘 처리를 보장하세요.

Comment on lines 20 to 22
suspend fun blockFriend(accessToken: Int): DefaultResponse<Unit>
suspend fun reportFriend(accessToken: Int, userId: Int): DefaultResponse<Unit>
abstract fun reportFriend(accessToken: Int): DefaultResponse<Unit>
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

잘못된 시그니처(Int 토큰, 인자 누락) 및 중복 선언(비-suspend) — 인터페이스 수정 필요

문제점:

  • 기존 메서드들과 달리 accessToken: Int 사용(일관성 붕괴).
  • blockFrienduserId 누락.
  • reportFriend가 suspend/비-suspend 중복 선언.
  • 인터페이스 내 abstract 키워드는 불필요(경고 유발).

수정 제안(diff):

-    suspend fun blockFriend(accessToken: Int): DefaultResponse<Unit>
-    suspend fun reportFriend(accessToken: Int, userId: Int): DefaultResponse<Unit>
-    abstract fun reportFriend(accessToken: Int): DefaultResponse<Unit>
+    suspend fun blockFriend(accessToken: String, userId: Int): DefaultResponse<Unit>
+    suspend fun reportFriend(accessToken: String, userId: Int): DefaultResponse<Unit>

참고:

  • 반환타입을 DefaultResponse<Unit>으로 신규 정책에 맞추려면, 구현부/Service 레이어와 일괄 정합성을 확보하세요. 기존의 MessageResponse 반환 API들과의 혼재는 호출부에서 분기 복잡도를 키울 수 있습니다.

아래 스크립트로 인터페이스-구현-서비스 간 시그니처가 일치하는지 확인해 주세요.


🏁 Script executed:

#!/bin/bash
# 인터페이스 선언 확인
rg -n "interface\s+FriendRepository" -A 50

# 구현체가 인터페이스를 정확히 구현하는지 확인
rg -n "class\s+FriendRepositoryImpl" -A 120 | sed -n '1,160p'

# VM/사용처가 어떤 시그니처를 기대하는지 확인
rg -n "FriendViewModel" -A 200 | rg -n "blockFriend|reportFriend" -A 3 -B 3

Length of output: 12634


수정 필요: FriendRepository의 시그니처(타입/인자) 정리 및 중복 제거

간단 요약: 인터페이스·구현체는 accessToken을 Int로 잘못 선언하고 userId가 누락/중복되어 있으며(또한 interface에 불필요한 abstract 선언 포함) 호출부는 userId만 전달하고 있습니다. 일관성 있게 accessToken은 String으로, block/report는 (accessToken: String, userId: Int) 형태로 통일하고 중복된 비-suspend 선언을 제거하세요.

문제 위치(검증 결과)

  • app/src/main/java/umc/onairmate/data/repository/repository/FriendRepository.kt (문제 선언)
  • app/src/main/java/umc/onairmate/data/repository/repositoryImpl/FriendRepositoryImpl.kt (구현체: 현재 TODO 및 중복 override)
  • app/src/main/java/umc/onairmate/ui/friend/FriendViewModel.kt (호출부: repository.blockFriend(userId) / repository.reportFriend(userId))
  • app/src/main/java/umc/onairmate/ui/friend/list/FriendListTabFragment.kt (ViewModel 호출 지점: viewModel.blockFriend(data.userId), viewModel.reportFriend(data.userId))

권장 수정(diff 예시)

  • 인터페이스 (FriendRepository.kt)
-    suspend fun blockFriend(accessToken: Int): DefaultResponse<Unit>
-    suspend fun reportFriend(accessToken: Int, userId: Int): DefaultResponse<Unit>
-    abstract fun reportFriend(accessToken: Int): DefaultResponse<Unit>
+    suspend fun blockFriend(accessToken: String, userId: Int): DefaultResponse<Unit>
+    suspend fun reportFriend(accessToken: String, userId: Int): DefaultResponse<Unit>
  • 구현체 (FriendRepositoryImpl.kt) — 중복 non-suspend 제거 및 타입/인자 수정
-    override suspend fun blockFriend(accessToken: Int): DefaultResponse<Unit> {
-        TODO("Not yet implemented")
-    }
-
-    override suspend fun reportFriend(accessToken: Int, userId: Int): DefaultResponse<Unit> {
-        TODO("Not yet implemented")
-    }
-
-    override fun reportFriend(accessToken: Int): DefaultResponse<Unit> {
-        TODO("Not yet implemented")
-    }
+    override suspend fun blockFriend(accessToken: String, userId: Int): DefaultResponse<Unit> {
+        return safeApiCall { api.blockFriend(accessToken, userId) }
+    }
+
+    override suspend fun reportFriend(accessToken: String, userId: Int): DefaultResponse<Unit> {
+        return safeApiCall { api.reportFriend(accessToken, userId) }
+    }
  • ViewModel 호출부 (FriendViewModel.kt) — accessToken 전달
-            val response = repository.blockFriend(userId)
+            val token = getAccessToken() ?: return@launch
+            val response = repository.blockFriend(token, userId)

(동일하게 reportFriend도 token을 첫 인자로 전달)

주의/추가

  • interface의 abstract 키워드 제거로 경고 해소하세요.
  • Service(FriendService) 레이어의 해당 엔드포인트 시그니처 및 반환 타입(DefaultResponse 여부)과도 함께 정합성 맞추세요.
  • 변경 시 ViewModel/Fragment 호출부 전역을 검색해 accessToken 인자 누락 부분을 모두 수정해야 합니다.

요약: 인터페이스·구현·사용처 모두 수정 필요 — 위 diff 대로 수정하고 FriendService/호출부 정합성 확인 후 커밋하세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
suspend fun blockFriend(accessToken: Int): DefaultResponse<Unit>
suspend fun reportFriend(accessToken: Int, userId: Int): DefaultResponse<Unit>
abstract fun reportFriend(accessToken: Int): DefaultResponse<Unit>
suspend fun blockFriend(accessToken: String, userId: Int): DefaultResponse<Unit>
suspend fun reportFriend(accessToken: String, userId: Int): DefaultResponse<Unit>
🤖 Prompt for AI Agents
In
app/src/main/java/umc/onairmate/data/repository/repository/FriendRepository.kt
around lines 20-22, the interface method signatures are incorrect and
duplicated: change accessToken parameter type from Int to String, make both
methods suspend fun blockFriend(accessToken: String, userId: Int):
DefaultResponse<Unit> and suspend fun reportFriend(accessToken: String, userId:
Int): DefaultResponse<Unit>, and remove the stray non-suspend/abstract
declaration and the `abstract` keyword on the interface; then update the
implementation in
app/src/main/java/umc/onairmate/data/repository/repositoryImpl/FriendRepositoryImpl.kt
to match (remove duplicate non-suspend overrides and implement the corrected
suspend signatures), update FriendService endpoint signatures/return types to be
consistent with DefaultResponse<Unit> if needed, and fix all call sites (e.g.,
app/src/main/java/umc/onairmate/ui/friend/FriendViewModel.kt and
app/src/main/java/umc/onairmate/ui/friend/list/FriendListTabFragment.kt) to pass
the accessToken (String) as the first argument when calling
blockFriend/reportFriend; search the repo for other usages and adjust
accordingly before committing.

Comment on lines 62 to 72
override suspend fun blockFriend(accessToken: Int): DefaultResponse<Unit> {
TODO("Not yet implemented")
}

override suspend fun reportFriend(accessToken: Int, userId: Int): DefaultResponse<Unit> {
TODO("Not yet implemented")
}

override fun reportFriend(accessToken: Int): DefaultResponse<Unit> {
TODO("Not yet implemented")
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

시그니처 불일치 및 미구현(TODO) 방치 — 런타임 크래시/호출부 불일치 위험

문제점:

  • accessToken 타입이 기존 메서드들과 다르게 Int로 선언됨(일관성 저하 및 컴파일 타임 오류 유발 가능).
  • blockFrienduserId 인자가 없어 실제 차단 대상 지정 불가.
  • reportFriend가 동일 이름으로 suspend/비-suspend 중복 선언됨(설계 오류).
  • 모두 TODO로 남아 런타임에 NotImplementedError 발생.

수정 제안(diff):

-    override suspend fun blockFriend(accessToken: Int): DefaultResponse<Unit> {
-        TODO("Not yet implemented")
-    }
+    override suspend fun blockFriend(accessToken: String, userId: Int): DefaultResponse<Unit> {
+        return safeApiCall { api.blockFriend(accessToken, userId) }
+    }

-    override suspend fun reportFriend(accessToken: Int, userId: Int): DefaultResponse<Unit> {
-        TODO("Not yet implemented")
-    }
+    override suspend fun reportFriend(accessToken: String, userId: Int): DefaultResponse<Unit> {
+        return safeApiCall { api.reportFriend(accessToken, userId) }
+    }

-    override fun reportFriend(accessToken: Int): DefaultResponse<Unit> {
-        TODO("Not yet implemented")
-    }
+    // 제거: 중복/비-suspend 선언 불필요

주의:

  • FriendService API 시그니처가 blockFriend(accessToken: String, userId: Int) / reportFriend(accessToken: String, userId: Int) 형태로 존재해야 합니다. 만약 MessageResponse를 반환한다면, 리턴 타입을 DefaultResponse<MessageResponse>로 맞추거나, DefaultResponse<Unit>으로 통일하고자 한다면 변환 래핑이 필요합니다.

다음 스크립트로 FriendService 정의와 사용처 시그니처 불일치를 확인해 주세요.


🏁 Script executed:

#!/bin/bash
# FriendService에 block/report API 유무 및 파라미터 타입 확인
rg -n "interface\s+FriendService" -A 200
rg -n "blockFriend\s*\(" -A 3 -B 3
rg -n "reportFriend\s*\(" -A 3 -B 3

# Repository/VM 사용처 점검
rg -n "blockFriend\(" -S -A 3 -B 3
rg -n "reportFriend\(" -S -A 3 -B 3

# accessToken이 Int로 선언된 곳 탐색(오타/불일치 탐지)
rg -n "accessToken\s*:\s*Int" -S

Length of output: 20198


🏁 Script executed:

#!/bin/bash
set -e
echo "== FriendService.kt =="
sed -n '1,240p' app/src/main/java/umc/onairmate/data/api/FriendService.kt || true
echo
echo "== FriendRepository.kt =="
sed -n '1,240p' app/src/main/java/umc/onairmate/data/repository/repository/FriendRepository.kt || true
echo
echo "== FriendRepositoryImpl.kt =="
sed -n '1,240p' app/src/main/java/umc/onairmate/data/repository/repositoryImpl/FriendRepositoryImpl.kt || true
echo
echo "== FriendViewModel.kt =="
sed -n '1,240p' app/src/main/java/umc/onairmate/ui/friend/FriendViewModel.kt || true

Length of output: 15036


필수 수정: FriendRepository 시그니처·미구현 메서드 정리 및 ViewModel 호출 불일치 수정 필요

검증 요약(간단):

  • FriendRepository/FriendRepositoryImpl에 accessToken이 Int로 잘못 선언되어 있고(타입·파라미터 불일치), block/report 메서드들이 TODO로 미구현입니다.
  • FriendRepository에 같은 이름의 reportFriend(비-suspend) 중복 선언이 존재합니다.
  • FriendViewModel은 repository.blockFriend(userId) / reportFriend(userId)로 토큰 없이 호출하고 있어 호출부/인터페이스 불일치가 발생합니다.
  • FriendService에는 block/report 관련 API 메서드가 정의되어 있지 않습니다. (따라서 repository에서 api.blockFriend/api.reportFriend를 바로 호출할 수 없음)

수정해야 할 위치(핵심):

  • app/src/main/java/umc/onairmate/data/repository/repository/FriendRepository.kt — 잘못된 시그니처/중복 제거
  • app/src/main/java/umc/onairmate/data/repository/repositoryImpl/FriendRepositoryImpl.kt — TODO 구현 및 중복 제거
  • app/src/main/java/umc/onairmate/ui/friend/FriendViewModel.kt — 토큰 획득 후 repository에 토큰·userId 전달하도록 수정
  • app/src/main/java/umc/onairmate/data/api/FriendService.kt — (백엔드에 해당 엔드포인트가 있다면) block/report API 정의 추가 필요

권장 변경(diff 예시, 최소 변경 방향):

--- a/.../repository/FriendRepository.kt
-    suspend fun inviteFriend( accessToken: String, friendId: Int): DefaultResponse<MessageResponse>
-    suspend fun blockFriend(accessToken: Int): DefaultResponse<Unit>
-    suspend fun reportFriend(accessToken: Int, userId: Int): DefaultResponse<Unit>
-    abstract fun reportFriend(accessToken: Int): DefaultResponse<Unit>
+    suspend fun inviteFriend( accessToken: String, friendId: Int): DefaultResponse<MessageResponse>
+    suspend fun blockFriend(accessToken: String, userId: Int): DefaultResponse<Unit>
+    suspend fun reportFriend(accessToken: String, userId: Int): DefaultResponse<Unit>
--- a/.../repositoryImpl/FriendRepositoryImpl.kt
-    override suspend fun blockFriend(accessToken: Int): DefaultResponse<Unit> {
-        TODO("Not yet implemented")
-    }
-
-    override suspend fun reportFriend(accessToken: Int, userId: Int): DefaultResponse<Unit> {
-        TODO("Not yet implemented")
-    }
-
-    override fun reportFriend(accessToken: Int): DefaultResponse<Unit> {
-        TODO("Not yet implemented")
-    }
+    override suspend fun blockFriend(accessToken: String, userId: Int): DefaultResponse<Unit> {
+        return safeApiCall { api.blockFriend(accessToken, userId) }
+    }
+
+    override suspend fun reportFriend(accessToken: String, userId: Int): DefaultResponse<Unit> {
+        return safeApiCall { api.reportFriend(accessToken, userId) }
+    }
--- a/.../ui/friend/FriendViewModel.kt
-    fun blockFriend(userId: Int) {
-        viewModelScope.launch {
-            val response = repository.blockFriend(userId)
-            if (response.isSuccessful) {
-                _result.value = "친구가 차단되었습니다."
-            }
-        }
-    }
+    fun blockFriend(userId: Int) {
+        viewModelScope.launch {
+            val token = getToken()
+            if (token == null) { Log.e(TAG, "토큰이 없습니다"); return@launch }
+            val response = repository.blockFriend(token, userId)
+            if (response.isSuccessful) _result.value = "친구가 차단되었습니다."
+        }
+    }

(동일하게 reportFriend도 token 획득 후 repository.reportFriend(token, userId)로 수정)

주의(꼭 확인할 것):

  • 위 구현에서 safeApiCall { api.blockFriend(...) } 를 사용하려면 FriendService에 대응하는 API 메서드 정의가 필요합니다. 현재 app/src/main/java/umc/onairmate/data/api/FriendService.kt 에는 block/report 메서드가 없습니다. 백엔드 엔드포인트 명·응답 타입(RawDefaultResponse 또는 MessageResponse 등)을 확인 후 FriendService에 정확한 시그니처를 추가하세요.
  • 반환 타입(DefaultResponse vs DefaultResponse)은 백엔드 응답에 맞게 통일하세요.
  • 중복된 비-suspend 선언(abstract fun reportFriend(...))은 제거해야 합니다.

결론: 인터페이스·구현·호출부(ViewModel) 모두 불일치·미구현 상태로, 백엔드 API 정의 확인 후 위 방향으로 수정이 필요합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
override suspend fun blockFriend(accessToken: Int): DefaultResponse<Unit> {
TODO("Not yet implemented")
}
override suspend fun reportFriend(accessToken: Int, userId: Int): DefaultResponse<Unit> {
TODO("Not yet implemented")
}
override fun reportFriend(accessToken: Int): DefaultResponse<Unit> {
TODO("Not yet implemented")
}
override suspend fun blockFriend(accessToken: String, userId: Int): DefaultResponse<Unit> {
return safeApiCall { api.blockFriend(accessToken, userId) }
}
override suspend fun reportFriend(accessToken: String, userId: Int): DefaultResponse<Unit> {
return safeApiCall { api.reportFriend(accessToken, userId) }
}

Comment on lines 15 to 16
fun onItemClick(friendData: FriendData)
fun onRequestItemClick(requestedData: RequestedFriendData)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

추가된 인터페이스 메서드로 인한 호환성(ABI/소스) 이슈 — 기본 구현 제공 권장

새 메서드가 추상으로 추가되어 기존 구현체 모두가 컴파일 에러에 노출됩니다. 인터페이스에 기본 구현을 제공하면 바이너리/소스 호환성을 유지할 수 있습니다.

적용 제안(diff):

-    fun onItemClick(friendData: FriendData)
-    fun onRequestItemClick(requestedData: RequestedFriendData)
+    fun onItemClick(friendData: FriendData) { /* no-op */ }
+    fun onRequestItemClick(requestedData: RequestedFriendData) { /* no-op */ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun onItemClick(friendData: FriendData)
fun onRequestItemClick(requestedData: RequestedFriendData)
fun onItemClick(friendData: FriendData) { /* no-op */ }
fun onRequestItemClick(requestedData: RequestedFriendData) { /* no-op */ }
🤖 Prompt for AI Agents
In app/src/main/java/umc/OnAirMate/ui/friend/list/FriendItemClickListener.kt
around lines 15-16, the newly added abstract method
onRequestItemClick(requestedData: RequestedFriendData) breaks existing
implementers; provide a default (empty) implementation in the interface so old
implementations remain source/binary compatible by turning the new method into a
non-abstract default method with an empty body, leaving onItemClick unchanged.

Comment on lines 15 to 21
private val context: Context,
private val itemClickListener: FriendItemClickListener,
onBlockClick: (FriendData) -> Unit,
onReportClick: (FriendData) -> Unit,
onAcceptRequest: (RequestedFriendData) -> Unit,
onRejectRequest: (RequestedFriendData) -> Unit
) : ListAdapter<Any, RecyclerView.ViewHolder>(FriendRVAdapterDiffCallback) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

생성자 콜백 람다가 저장/전달되지 않아 동작하지 않습니다.

  • onBlockClick/onReportClick/onAcceptRequest/onRejectRequest 파라미터가 프로퍼티로 저장되지 않고, ViewHolder에 전달도 되지 않습니다.
  • 현재 상태에선 사용자 액션이 전파되지 않습니다.

콜백을 프로퍼티로 저장하고, 적절한 ViewHolder 생성자에 전달해 사용하세요.

 class FriendListRVAdapter(
-    private val context: Context,
-    private val itemClickListener: FriendItemClickListener,
-    onBlockClick: (FriendData) -> Unit,
-    onReportClick: (FriendData) -> Unit,
-    onAcceptRequest: (RequestedFriendData) -> Unit,
-    onRejectRequest: (RequestedFriendData) -> Unit
+    private val context: Context,
+    private val itemClickListener: FriendItemClickListener,
+    private val onBlockClick: (FriendData) -> Unit,
+    private val onReportClick: (FriendData) -> Unit,
+    private val onAcceptRequest: (RequestedFriendData) -> Unit,
+    private val onRejectRequest: (RequestedFriendData) -> Unit
 ) : ListAdapter<Any, RecyclerView.ViewHolder>(FriendRVAdapterDiffCallback) {
@@
-            TYPE_FRIEND -> {
+            TYPE_FRIEND -> {
                 val binding = RvItemFriendBinding.inflate(inflater, parent, false)
-                FriendViewHolder(binding, context, itemClickListener)
+                FriendViewHolder(binding, context, itemClickListener, onBlockClick, onReportClick)
             }
-            TYPE_REQUEST -> {
+            TYPE_REQUEST -> {
                 val binding = RvItemFriendRequestBinding.inflate(inflater, parent, false)
-                FriendRequestViewHolder(binding, context, itemClickListener)
+                FriendRequestViewHolder(binding, context, itemClickListener, onAcceptRequest, onRejectRequest)
             }

추가로, ViewHolder들 내부에서 콜백을 실제로 바인딩(버튼 클릭 등)에 연결하는지 확인해 주세요. 필요 시 해당 파일들도 패치 제안 가능합니다.

Also applies to: 41-48

🤖 Prompt for AI Agents
In app/src/main/java/umc/onairmate/ui/friend/list/FriendListRVAdapter.kt around
lines 15-21 (also apply same change to 41-48), the constructor lambda parameters
onBlockClick/onReportClick/onAcceptRequest/onRejectRequest are not stored as
properties or passed to ViewHolders, so user actions never propagate; fix by
declaring these lambdas as class properties (e.g., private val onBlockClick:
(FriendData) -> Unit, etc.), update onCreateViewHolder to pass the appropriate
callbacks into each ViewHolder constructor, and ensure each ViewHolder binds
those callbacks to UI elements (button clicks) when binding data.

Comment on lines 23 to 20
private val items = mutableListOf<Any>()

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

ListAdapter와 수동 items 리스트를 혼용하여 치명적 불일치 발생 (크래시 가능).

  • getItemCount()는 items.size를 반환하지만, onBindViewHolder/getItemViewType은 ListAdapter의 getItem(position)을 사용합니다.
  • submitList가 한 번도 호출되지 않아 currentList는 비어 있으며, 인덱스 접근 시 IndexOutOfBounds 예외 발생합니다.
  • 또한 notifyDataSetChanged()는 DiffUtil 이점을 무력화합니다.

ListAdapter를 유지한다면 내부 items를 제거하고 submitList를 사용하세요.

-    private val items = mutableListOf<Any>()
+    // DiffUtil 기반 ListAdapter 사용: 내부 items 리스트 제거

-    override fun getItemCount(): Int = items.size
+    // getItemCount는 ListAdapter가 currentList로 관리하므로 오버라이드 불필요

-    fun initFriendList(list: List<FriendData>) {
-        items.clear()
-        items.addAll(list)
-        notifyDataSetChanged()
-    }
+    fun initFriendList(list: List<FriendData>) {
+        submitList(list.map { it as Any })
+    }

-    fun initRequestList(list: List<RequestedFriendData>) {
-        items.clear()
-        items.addAll(list)
-        notifyDataSetChanged()
-    }
+    fun initRequestList(list: List<RequestedFriendData>) {
+        submitList(list.map { it as Any })
+    }

Also applies to: 60-72

🤖 Prompt for AI Agents
In app/src/main/java/umc/onairmate/ui/friend/list/FriendListRVAdapter.kt around
lines 23-24 (also apply changes to lines 60-72): the adapter mixes a manual
mutable items list with ListAdapter's currentList causing IndexOutOfBounds and
negating DiffUtil; remove the internal items property, stop calling
notifyDataSetChanged(), and ensure all reads
(getItemCount/getItemViewType/onBindViewHolder) use ListAdapter's APIs
(getItem(position) / currentList.size as appropriate); update call sites to use
adapter.submitList(newList) when updating data so DiffUtil works correctly, or
if you prefer manual control, convert the class to extend RecyclerView.Adapter
and keep the manual list and notify* calls consistently.

Comment on lines 94 to 110
viewModel.result.observe(viewLifecycleOwner, Observer { message ->
if (message == null) return@Observer
if (message == "친구가 삭제되었습니다.") friendChatViewModel.deleteFriend(friendId,user.userId)
if (message == "친구가 삭제되었습니다.") friendChatViewModel.deleteFriend(friendId, user.userId)
initData()
Toast.makeText(requireContext(),message, Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
viewModel.clearResult()
})
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

문자열 비교로 분기하는 로직은 취약하며 friendId가 설정되지 않습니다.

  • "친구가 삭제되었습니다." 같은 문자열로 분기하는 방식은 변경에 취약합니다.
  • friendId는 어디에서도 설정되지 않아 0으로 전달될 가능성이 큽니다.

권장:

  • ViewModel의 결과를 문자열이 아닌 이벤트(예: sealed class)로 분리하세요.
  • 삭제 수행 시점에 friendId를 설정하고, 이벤트에 포함하여 전달하세요.

예시(개념):

  • FriendViewModel.result 대신 LiveData 사용
  • FriendEvent.FriendDeleted(friendId), FriendEvent.Message(text) 등으로 구분

Comment on lines 126 to 147
itemClickListener = object : FriendItemClickListener {
override fun onItemClick(friendData: FriendData) {
// 친구 아이템 클릭 시 동작
}
override fun onRequestItemClick(requestedData: RequestedFriendData) {
// 친구 요청 아이템 클릭 시 동작
}

override fun clickCollection(data: FriendData) {
// 인탠트 필요
}
override fun clickDelete(data: FriendData) {
val text = data.nickname+"님을 친구 목록에서 삭제하시겠습니까?"
val textList = listOf(text,"","아니오")
friendId = data.userId
showPopup(text =textList, left = {
viewModel.deleteFriend(data.userId) }, right = {} )
}
override fun clickMessage(data: FriendData) {
// TODO("Not yet implemented")
}

override fun clickBlock(data: FriendData) {
val text = data.nickname+"님을 차단하시겠습니까?"
val textList = listOf(text,"","아니오")
showPopup(text =textList, left = { }, right = {} )
}
override fun acceptRequest(data: RequestedFriendData) {
TODO("Not yet implemented")
}

override fun clickCollection(data: FriendData) {
TODO("Not yet implemented")
}

override fun clickDelete(data: FriendData) {
TODO("Not yet implemented")
}

override fun clickBlock(data: FriendData) {
TODO("Not yet implemented")
}

override fun clickReport(data: FriendData) {
val text = data.nickname+"님을 신고하시겠습니까?"
val textList = listOf(text,"","아니오")
showPopup(text =textList, left = { }, right = {} )
override fun clickReport(data: FriendData) {
TODO("Not yet implemented")
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

itemClickListener의 TODO는 런타임 크래시를 유발합니다. 최소 안전 동작으로 대체하세요.

TODO("Not yet implemented")는 호출 시 즉시 예외가 발생합니다. 아래처럼 안전한 기본 구현 또는 실제 동작으로 교체하세요.

-                override fun onItemClick(friendData: FriendData) {
-                    // 친구 아이템 클릭 시 동작
-                }
+                override fun onItemClick(friendData: FriendData) {
+                    // 필요 시 채팅/프로필 이동 구현. 미구현이면 no-op
+                }
@@
-                override fun onRequestItemClick(requestedData: RequestedFriendData) {
-                    // 친구 요청 아이템 클릭 시 동작
-                }
+                override fun onRequestItemClick(requestedData: RequestedFriendData) {
+                    // 필요 시 상세 보기. 미구현이면 no-op
+                }
@@
-                override fun clickMessage(data: FriendData) {
-                    // TODO("Not yet implemented")
-                }
+                override fun clickMessage(data: FriendData) {
+                    // 미구현이면 no-op
+                }
@@
-                override fun acceptRequest(data: RequestedFriendData) {
-                    TODO("Not yet implemented")
-                }
+                override fun acceptRequest(data: RequestedFriendData) {
+                    viewModel.acceptFriend(data.userId, "ACCEPT")
+                }
@@
-                override fun clickCollection(data: FriendData) {
-                    TODO("Not yet implemented")
-                }
+                override fun clickCollection(data: FriendData) {
+                    // 미구현이면 no-op
+                }
@@
-                override fun clickDelete(data: FriendData) {
-                    TODO("Not yet implemented")
-                }
+                override fun clickDelete(data: FriendData) {
+                    friendId = data.userId
+                    showPopup(
+                        listOf("${data.nickname}님을 친구 목록에서 삭제하시겠습니까?", "삭제", "취소"),
+                        right = { viewModel.deleteFriend(data.userId) },
+                        left = { /* no-op */ }
+                    )
+                }
@@
-                override fun clickBlock(data: FriendData) {
-                    TODO("Not yet implemented")
-                }
+                override fun clickBlock(data: FriendData) {
+                    TwoButtonPopup.showBlockPopup(parentFragmentManager, data.nickname) {
+                        viewModel.blockFriend(data.userId)
+                    }
+                }
@@
-                override fun clickReport(data: FriendData) {
-                    TODO("Not yet implemented")
-                }
+                override fun clickReport(data: FriendData) {
+                    TwoButtonPopup.showReportPopup(parentFragmentManager, data.nickname) {
+                        viewModel.reportFriend(data.userId)
+                    }
+                }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/src/main/java/umc/onairmate/ui/friend/list/FriendListTabFragment.kt
around lines 126 to 157, the itemClickListener implementation uses TODO("Not yet
implemented") in several overrides which throws at runtime; replace those TODOs
with safe default behavior (no-op or lightweight logging/toast) or real
implementations: specifically update clickMessage, acceptRequest,
clickCollection, clickDelete, clickBlock, and clickReport to perform
non-throwing actions (e.g., log the event and return or show a user-facing
message) so the app no longer crashes when those callbacks are invoked.

@jiyoung02 jiyoung02 force-pushed the feature/온보딩-프로필신고및차단 branch from 3cb310e to d4a2755 Compare August 14, 2025 15:49
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: 7

🔭 Outside diff range comments (1)
app/src/main/java/umc/onairmate/module/NetworkModule.kt (1)

41-45: "Connection: close" 인터셉터는 제거하거나 Debug에서만 활성화하세요 — retryOnConnectionFailure(false) 의도 확인 필요

확인 결과 app/src/main/java/umc/onairmate/module/NetworkModule.kt에 closeInterceptor(41-45)와 addNetworkInterceptor(closeInterceptor) 및 retryOnConnectionFailure(false)(47-54)가 존재합니다. "Connection: close"는 커넥션 재사용(HTTP/2/커넥션 풀)을 막아 성능/전력에 악영향을 줄 수 있으므로, 서버 제약으로 꼭 필요하지 않다면 제거하거나 Debug 빌드에서만 적용하세요. retryOnConnectionFailure(false)는 일시적 네트워크 장애에 대한 자동 재시도를 막으므로 의도를 명확히 해 주세요.

수정이 필요한 위치

  • app/src/main/java/umc/onairmate/module/NetworkModule.kt — lines 41-45 (closeInterceptor 정의)
  • app/src/main/java/umc/onairmate/module/NetworkModule.kt — lines 47-54 (addNetworkInterceptor / retryOnConnectionFailure)

제안되는 변경(diff):

-        return OkHttpClient.Builder()
-            .connectTimeout(40, TimeUnit.SECONDS)
-            .readTimeout(40, TimeUnit.SECONDS)
-            .writeTimeout(40, TimeUnit.SECONDS)
-            .addInterceptor(interceptor)
-            .addNetworkInterceptor(closeInterceptor)
-            .retryOnConnectionFailure(false)
-            .build()
+        val builder = OkHttpClient.Builder()
+            .connectTimeout(40, TimeUnit.SECONDS)
+            .readTimeout(40, TimeUnit.SECONDS)
+            .writeTimeout(40, TimeUnit.SECONDS)
+            .addInterceptor(interceptor)
+            .retryOnConnectionFailure(false)
+
+        if (BuildConfig.DEBUG) {
+            builder.addNetworkInterceptor(closeInterceptor)
+        }
+
+        return builder.build()

추가 권장사항: retryOnConnectionFailure(false)의 변경(기본 true로 유지 또는 의도 명시)을 검토해 주세요.

🧹 Nitpick comments (11)
app/src/main/java/umc/onairmate/data/model/entity/NicknameData.kt (1)

5-11: 응답 모델의 nullability 확인 필요 (특히 message).

백엔드가 항상 message를 내려주지 않는 경우가 많습니다. 현재 message가 비-null String으로 선언되어 있어, 필드가 누락되면 파싱 예외가 발생할 수 있습니다. 가능하면 message를 nullable(String?)로 두거나 기본값을 부여하는 방안을 검토해 주세요.

예시:

 data class NicknameData(
     @SerializedName("available")
     val available: Boolean,

     @SerializedName("message")
-    val message: String
+    val message: String?
 )

혹은

-    val message: String
+    val message: String = ""
app/src/main/java/umc/onairmate/module/NetworkModule.kt (1)

25-27: Qualifier 네이밍이 OkHttpClient에도 적용되어 혼동 여지.

@BaseRetrofit가 OkHttpClient와 Retrofit 둘 다에 붙어 있어 의미가 다소 모호합니다. 별도 용도의 클라이언트/레트로핏이 추가될 계획이 있다면, @BaseOkHttp / @BaseRetrofit처럼 역할에 맞춘 구분자를 쓰는 편이 유지보수성에 좋습니다. 당장 문제는 아니므로 추후 정리 제안드립니다.

Also applies to: 35-35, 59-61

app/src/main/java/umc/onairmate/data/api/NicknameService.kt (1)

11-16: 사용자 입력을 Path로 받는 경우 특수문자/슬래시 포함 닉네임 처리 검증 필요.

Retrofit은 기본적으로 @path 인자를 URL 인코딩하지만, 닉네임에 슬래시(/) 등 경로 구분 문자가 포함되면 의도치 않은 라우팅이 발생할 수 있습니다. 서버 스펙이 확실하다면 현 상태로도 무방하나, 안전 측면에선 Query 파라미터로 받는 것을 권장합니다.

예: @GET("auth/check-nickname") suspend fun checkNickname(@Query("nickname") nickname: String): NicknameResponse

또한 HTTP 에러 코드의 세부 처리(예: 409 Conflict)를 해야 한다면 Response<NicknameResponse>를 반환받아 status code 기반 분기하는 방식도 고려해 주세요.

app/src/main/java/umc/onairmate/data/repository/repository/NicknameRepository.kt (1)

3-5: 메서드 반환 의미(중복이면 true)를 KDoc으로 명시해 혼동 최소화.

isNicknameDuplicated는 이름상 의미가 분명하지만, 호출부에서 true/false 기준이 바뀌면 UX에 즉시 영향이 있습니다. KDoc으로 계약을 명확히 해 두는 것을 권장합니다.

 interface NicknameRepository {
-    suspend fun isNicknameDuplicated(nickname: String): Boolean
+    /**
+     * 닉네임이 '중복'이면 true, 사용 가능하면 false를 반환합니다.
+     */
+    suspend fun isNicknameDuplicated(nickname: String): Boolean
 }
app/src/main/java/umc/onairmate/data/model/response/NicknameResponse.kt (1)

6-12: 응답 모델을 data class로 전환 권장.

동등성/디버깅/복사(copy) 활용성을 위해 응답 DTO는 data class가 적합합니다. 또한 실패 시 data가 누락될 수 있다면 nullable 처리도 검토해 주세요.

-class NicknameResponse (
+data class NicknameResponse (
     @SerializedName("success")
     val success: Boolean,

     @SerializedName("data")
-    val data: NicknameData
+    val data: NicknameData
 )

data nullability에 대해 서버 스펙이 불확실하다면 다음과 같이 변경하는 방안도 있습니다(레포지토리에서 null-safe 처리 필요):

data class NicknameResponse(
  @SerializedName("success") val success: Boolean,
  @SerializedName("data") val data: NicknameData?
)
app/src/main/java/umc/onairmate/data/repository/repositoryImpl/NicknameRepositoryImpl.kt (1)

18-19: Log 태그 상수화 및 공통 로거(Timber) 사용 고려

반복 사용될 수 있는 태그 문자열은 상수로 빼거나 프로젝트에서 Timber를 사용한다면 대체하는 것이 좋습니다.

app/src/main/java/umc/onairmate/ui/pop_up/ChangeNicknameViewModel.kt (1)

15-19: 콜백 기반 API 대신 StateFlow/Livedata로 단방향 상태 제공 제안

UI에서 중복 체크 상태(로딩/성공/중복/에러)를 관찰 가능하도록 StateFlow/LiveData로 노출하는 편이 테스트/구독/구조화 측면에서 유리합니다.

app/src/main/java/umc/onairmate/module/RepositoryModule.kt (1)

73-84: Provider 함수 네이밍 일관성 제안(provide ↔ provides 혼재)

다른 함수들이 providesXRepository를 쓰는 반면 본 블록은 provideJoinRepository, provideNicknameRepository로 혼재되어 있습니다. 일관된 네이밍으로 가독성을 높이는 것을 권장합니다.

예시:

-    fun provideJoinRepository(
+    fun providesJoinRepository(
     joinService: JoinService
     ): JoinRepository = JoinRepositoryImpl(joinService)

-    fun provideNicknameRepository(
+    fun providesNicknameRepository(
         nicknameService: NicknameService
     ): NicknameRepository = NicknameRepositoryImpl(nicknameService)
app/src/main/java/umc/onairmate/ui/pop_up/ChangeNicknamePopup.kt (3)

46-61: TextWatcher 빈 메서드 제거: doAfterTextChanged로 간결화

detekt가 지적한 빈 블록을 제거하고 doAfterTextChanged 확장 함수를 사용하면 간결하고 안전합니다.

-        binding.editNickname.addTextChangedListener(object : TextWatcher {
-            override fun afterTextChanged(s: Editable?) {
-                val input = s.toString()
-                val isValid = input.length in 3..10
-
-                binding.checkNickname.setBackgroundResource(
-                    if (isValid) R.drawable.bg_btn_main
-                    else R.drawable.bg_btn_disabled
-                )
-                binding.checkNickname.isEnabled = isValid
-            }
-
-            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
-            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
-        })
+        binding.editNickname.doAfterTextChanged { s ->
+            val input = s?.toString()?.trim().orEmpty()
+            val isValid = input.length in 3..10
+            binding.checkNickname.setBackgroundResource(
+                if (isValid) R.drawable.bg_btn_main else R.drawable.bg_btn_disabled
+            )
+            binding.checkNickname.isEnabled = isValid
+        }

추가 import(파일 상단):

import androidx.core.widget.doAfterTextChanged

82-113: 대량의 주석 처리된 코드 제거 권장

주석된 대체 구현은 히스토리에서 확인 가능하므로 소스에는 남기지 않는 것이 가독성에 좋습니다.

-            /*
-            viewLifecycleOwner.lifecycleScope.launch {
-                val isDuplicated = withContext(Dispatchers.IO) {
-                    repository.isNicknameDuplicated(nickname)
-                }
-
-                if (isDuplicated) {
-                    Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show()
-                    binding.editNickname.text.clear()
-                    binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
-                    binding.checkNickname.isEnabled = false
-                } else {
-                    Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show()
-                }
-            }
-            */
-
-//            viewLifecycleOwner.lifecycleScope.launch {
-//                val isDuplicated = withContext(Dispatchers.IO) {
-//                    repository.isNicknameDuplicated(nickname)
-//                }
-//
-//
-//                if (isDuplicated) {
-//                    Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show()
-//                    binding.editNickname.text.clear()
-//                    binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
-//                    binding.checkNickname.isEnabled = false
-//                } else {
-//                    Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show()
-//                }
-//            }

66-79: 문자열 하드코딩 제거(리소스화)

토스트 메시지를 strings.xml로 이동해 다국어/일관성 관리가 가능하도록 해주세요.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3cb310e and adfaa89.

📒 Files selected for processing (11)
  • app/src/main/java/umc/onairmate/data/api/NicknameService.kt (1 hunks)
  • app/src/main/java/umc/onairmate/data/model/entity/NicknameData.kt (1 hunks)
  • app/src/main/java/umc/onairmate/data/model/response/NicknameResponse.kt (1 hunks)
  • app/src/main/java/umc/onairmate/data/repository/repository/NicknameRepository.kt (1 hunks)
  • app/src/main/java/umc/onairmate/data/repository/repositoryImpl/NicknameRepositoryImpl.kt (1 hunks)
  • app/src/main/java/umc/onairmate/module/NetworkModule.kt (1 hunks)
  • app/src/main/java/umc/onairmate/module/RepositoryModule.kt (5 hunks)
  • app/src/main/java/umc/onairmate/module/ServiceModule.kt (2 hunks)
  • app/src/main/java/umc/onairmate/ui/pop_up/ChangeNicknamePopup.kt (1 hunks)
  • app/src/main/java/umc/onairmate/ui/pop_up/ChangeNicknameViewModel.kt (1 hunks)
  • app/src/main/res/layout/fragment_profile.xml (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/res/layout/fragment_profile.xml
🧰 Additional context used
🪛 detekt (1.23.8)
app/src/main/java/umc/onairmate/ui/pop_up/ChangeNicknameViewModel.kt

[warning] 24-24: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

app/src/main/java/umc/onairmate/ui/pop_up/ChangeNicknamePopup.kt

[warning] 58-58: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)


[warning] 59-59: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)

🔇 Additional comments (3)
app/src/main/java/umc/onairmate/data/repository/repositoryImpl/NicknameRepositoryImpl.kt (1)

14-16: 확인 완료 — response.data 및 available은 non-nullable입니다

확인 결과 response 객체의 data 필드는 NicknameData로 non-nullable이며, NicknameData.available 또한 Boolean(비-null)으로 선언되어 있어 NPE 위험이 없습니다.

  • app/src/main/java/umc/onairmate/data/model/response/NicknameResponse.kt:11 — val data: NicknameData
  • app/src/main/java/umc/onairmate/data/model/response/NicknameCheckResponse.kt:9 — val data: NicknameData
  • app/src/main/java/umc/onairmate/data/model/response/NicknameCheckResponse.kt:13-15 — data class NicknameData { @SerializedName("available") val available: Boolean }
  • app/src/main/java/umc/onairmate/data/model/entity/NicknameData.kt:5-7 — data class NicknameData { @SerializedName("available") val available: Boolean }

따라서 아래 스니펫은 변경 불필요합니다:

            val response = api.checkNickname(nickname)
            // available == true 면 사용 가능한 닉네임, 중복 아님 → 따라서 중복 여부는 반대(!)
            !response.data.available
app/src/main/java/umc/onairmate/module/ServiceModule.kt (1)

63-72: 새 Service provider 추가 변경사항 LGTM

nicknameApi, joinApi 모두 @singleton 범위와 @BaseRetrofit 한정자를 일관되게 사용하고 있으며, 빌더 헬퍼로 생성되는 패턴도 적절합니다.

app/src/main/java/umc/onairmate/module/RepositoryModule.kt (1)

41-41: HomeRepository provider 변경사항 문제 없음

DI 범위와 시그니처가 기존 패턴과 일치합니다.

Comment on lines +12 to +21
override suspend fun isNicknameDuplicated(nickname: String): Boolean {
return try {
val response = api.checkNickname(nickname)
// available == true 면 사용 가능한 닉네임, 중복 아님 → 따라서 중복 여부는 반대(!)
!response.data.available
} catch (e: Exception) {
Log.e("NicknameRepository", "닉네임 중복 검사 실패", e)
// 실패 시 기본값 false 또는 true 선택 가능 (보통 실패는 중복 아님 false로 처리)
false
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

에러를 ‘중복 아님(false)’으로 간주하면 잘못된 승인 위험이 큽니다

네트워크/서버 오류 시 false를 반환하면 중복된 닉네임이 ‘사용 가능’으로 오판될 수 있습니다. 최소 조치로 실패 시 보수적으로 true(중복) 처리하고, 후속 PR에서 3-state(Result/에러 구분)로 개선하는 것을 권장합니다.

다음과 같이 변경을 제안합니다:

         return try {
             val response = api.checkNickname(nickname)
             // available == true 면 사용 가능한 닉네임, 중복 아님 → 따라서 중복 여부는 반대(!)
             !response.data.available
         } catch (e: Exception) {
             Log.e("NicknameRepository", "닉네임 중복 검사 실패", e)
-            // 실패 시 기본값 false 또는 true 선택 가능 (보통 실패는 중복 아님 false로 처리)
-            false
+            // 실패 시 보수적으로 중복(true) 처리하여 잘못된 승인 방지
+            true
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
override suspend fun isNicknameDuplicated(nickname: String): Boolean {
return try {
val response = api.checkNickname(nickname)
// available == true 면 사용 가능한 닉네임, 중복 아님 → 따라서 중복 여부는 반대(!)
!response.data.available
} catch (e: Exception) {
Log.e("NicknameRepository", "닉네임 중복 검사 실패", e)
// 실패 시 기본값 false 또는 true 선택 가능 (보통 실패는 중복 아님 false로 처리)
false
}
override suspend fun isNicknameDuplicated(nickname: String): Boolean {
return try {
val response = api.checkNickname(nickname)
// available == true 면 사용 가능한 닉네임, 중복 아님 → 따라서 중복 여부는 반대(!)
!response.data.available
} catch (e: Exception) {
Log.e("NicknameRepository", "닉네임 중복 검사 실패", e)
// 실패 시 보수적으로 중복(true) 처리하여 잘못된 승인 방지
true
}
🤖 Prompt for AI Agents
In
app/src/main/java/umc/onairmate/data/repository/repositoryImpl/NicknameRepositoryImpl.kt
around lines 12 to 21, the catch block currently returns false on any exception
which treats failures as "not duplicated" — change the catch to return true
(treat failure conservatively as duplicated) and keep or improve the Log.e call
to still record the exception; optionally add a TODO comment to follow up with a
3-state result/error refactor in a later PR.

Comment on lines 70 to 72
private inline fun <reified T> Retrofit.buildService(): T{
return this.create(T::class.java)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

private 확장 함수는 다른 모듈/파일에서 사용할 수 없어 DI 사용 시 컴파일 오류 가능.

ServiceModule 등에서 retrofit.buildService<...>()를 사용한다면, 현재 private 가시성으로는 import/호출이 불가능합니다. 확장 함수 가시성을 public으로 변경해 외부에서 사용할 수 있도록 해야 합니다.

-    private inline fun <reified T> Retrofit.buildService(): T{
-        return this.create(T::class.java)
-    }
+    inline fun <reified T> Retrofit.buildService(): T {
+        return this.create(T::class.java)
+    }

또는, 팀 표준에 맞춰 top-level 함수로 분리(파일 전역 public)하면 import ...buildServiceretrofit.buildService<>()로 자연스럽게 사용할 수 있습니다. 필요 시 해당 리팩터링 패치도 제공하겠습니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private inline fun <reified T> Retrofit.buildService(): T{
return this.create(T::class.java)
}
inline fun <reified T> Retrofit.buildService(): T {
return this.create(T::class.java)
}
🤖 Prompt for AI Agents
In app/src/main/java/umc/onairmate/module/NetworkModule.kt around lines 70-72,
the extension function is declared private which prevents use from other
files/modules and will cause compilation errors when using it in DI or
ServiceModule; make the extension function public (remove the private modifier)
so it can be imported and called externally, or alternatively move it to a
top-level file as a public function (keeping inline and reified) so other
modules can import and call retrofit.buildService<T>().

💡 Verification agent

🧩 Analysis chain

buildService 확장 함수 사용 위치 자동 검증 제안.

ServiceModule 등에서 실제로 이 함수가 import되어 사용되는지 확인이 필요합니다. 아래 스크립트로 사용처를 빠르게 점검할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# buildService 확장 함수 사용처 및 import 확인
rg -n --no-heading $'buildService<' -A 2
rg -n --no-heading $'^import .+NetworkModule\.buildService$' -A 1

Length of output: 108


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Searching for 'buildService' occurrences..."
rg -n --no-heading --hidden 'buildService' || true
echo
echo "Searching for generic calls 'buildService<'..."
rg -n --no-heading --hidden 'buildService<' || true
echo
echo "Searching for '.buildService'..."
rg -n --no-heading --hidden '\.buildService' || true
echo
echo "Show NetworkModule.kt content (first 300 lines if exists)..."
FILE=app/src/main/java/umc/onairmate/module/NetworkModule.kt
if [ -f "$FILE" ]; then
  sed -n '1,300p' "$FILE"
else
  echo "File not found: $FILE"
fi

Length of output: 5259


buildService 확장 함수 중복 선언 — NetworkModule.kt의 함수 제거 권고

검증 결과: ServiceModule.kt에 동일한 확장 함수가 선언되어 여러 곳에서 사용되고 있으며, NetworkModule.kt(라인 70)의 buildService는 레포 전역에서 호출처가 발견되지 않습니다.

  • app/src/main/java/umc/onairmate/module/ServiceModule.kt
    • 선언: private inline fun Retrofit.buildService(): T { ... } (라인 ~23)
    • 사용: retrofit.buildService() 호출 다수 (라인 31, 37, 43, 49, 54, 60, 66, 71)
  • app/src/main/java/umc/onairmate/module/NetworkModule.kt
    • 선언: private inline fun Retrofit.buildService(): T { return this.create(T::class.java) } (라인 70)
    • 사용처: 없음 (전역 검색 결과)

제안 조치: NetworkModule.kt의 중복 확장 함수를 제거하거나(권장) 한 곳으로 통합해 중복을 제거하세요. 의도적 분리라면 이유를 주석으로 명시해 주세요.

문맥용 코드(참고)

private inline fun <reified T> Retrofit.buildService(): T{
    return this.create(T::class.java)
}
🤖 Prompt for AI Agents
In app/src/main/java/umc/onairmate/module/NetworkModule.kt around lines 70 to
72, remove the duplicate private inline fun <reified T> Retrofit.buildService()
extension (or replace it with a single import/reference to the implementation in
ServiceModule.kt) because ServiceModule.kt already declares and is used
throughout the repo; if the duplicate was intentional, instead add a comment
explaining the reason and keep only one declaration accessible where needed to
avoid redundancy.

Comment on lines +16 to 18
import umc.onairmate.data.api.NicknameService
import umc.onairmate.data.repository.NicknameRepository
import umc.onairmate.data.repository.repository.FriendRepository
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

NicknameRepository 패키지 경로 확인 필요(다른 레포지토리들과 불일치 가능성)

다른 레포지토리 인터페이스는 ...data.repository.repository.* 경로를 사용하지만, NicknameRepository...data.repository.*로 임포트되어 있습니다. 의도된 구조인지 확인해 주세요. 잘못되면 컴파일 에러 발생합니다.

다음 스크립트로 인터페이스의 실제 패키지/경로를 확인해 주세요:


🏁 Script executed:

#!/bin/bash
# NicknameRepository 선언 위치와 패키지명 확인
rg -n -A 2 -B 2 $'^package .*NicknameRepository' app | sed -n '1,200p'
rg -n $'interface\\s+NicknameRepository' app -A 3 -B 3

Length of output: 675


조치 필요: NicknameRepository 패키지/파일 위치 불일치 확인

확인 결과: 파일은 app/src/main/java/umc/onairmate/data/repository/repository/NicknameRepository.kt에 위치하지만 내부 패키지 선언은
package umc.onairmate.data.repository
로 되어 있어 디렉터리 구조와 패키지 선언이 불일치합니다. 현재 RepositoryModule.kt의 import는 선언된 패키지와 일치하지만(따라서 즉시 컴파일 에러는 아님) 구조상 혼동을 초래하므로 정리 권장합니다.

수정 권고 (택1)

  • 패키지 수정: NicknameRepository.kt의 패키지를 디렉터리 구조에 맞게 수정
    • 파일: app/src/main/java/umc/onairmate/data/repository/repository/NicknameRepository.kt
    • 변경: package umc.onairmate.data.repository → package umc.onairmate.data.repository.repository
    • 이후 RepositoryModule.kt의 import를 아래로 변경:
      • 기존: import umc.onairmate.data.repository.NicknameRepository
      • 변경: + import umc.onairmate.data.repository.repository.NicknameRepository
  • 또는 파일 이동: 패키지 선언을 유지하려면 파일을 app/src/main/java/umc/onairmate/data/repository/로 이동

수정할 파일(우선 확인 대상)

  • app/src/main/java/umc/onairmate/data/repository/repository/NicknameRepository.kt
  • app/src/main/java/umc/onairmate/module/RepositoryModule.kt (import 업데이트 필요 시)
🤖 Prompt for AI Agents
In app/src/main/java/umc/onairmate/module/RepositoryModule.kt around lines
16-18: the NicknameRepository class is located at
app/src/main/java/umc/onairmate/data/repository/repository/NicknameRepository.kt
but its file declares package umc.onairmate.data.repository, causing a
directory/package mismatch; fix by updating the package declaration in that file
to package umc.onairmate.data.repository.repository and then update the import
in RepositoryModule.kt from import
umc.onairmate.data.repository.NicknameRepository to import
umc.onairmate.data.repository.repository.NicknameRepository (alternatively, move
the NicknameRepository.kt file up one directory to match the existing package if
you prefer the original package).

Comment on lines +63 to +69
val nickname = binding.editNickname.text.toString()

if (nickname.length !in 3..10) {
Toast.makeText(requireContext(), "닉네임은 3자 이상 10자 이하여야 합니다.", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

공백 허용/길이 검증 개선: trim 적용

공백만 입력(예: " ")도 길이 조건을 통과할 수 있습니다. trim 후 길이 검증으로 수정하세요.

-            val nickname = binding.editNickname.text.toString()
+            val nickname = binding.editNickname.text.toString().trim()
 
-            if (nickname.length !in 3..10) {
+            if (nickname.length !in 3..10) {
                 Toast.makeText(requireContext(), "닉네임은 3자 이상 10자 이하여야 합니다.", Toast.LENGTH_SHORT).show()
                 return@setOnClickListener
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val nickname = binding.editNickname.text.toString()
if (nickname.length !in 3..10) {
Toast.makeText(requireContext(), "닉네임은 3자 이상 10자 이하여야 합니다.", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
val nickname = binding.editNickname.text.toString().trim()
if (nickname.length !in 3..10) {
Toast.makeText(requireContext(), "닉네임은 3자 이상 10자 이하여야 합니다.", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
🤖 Prompt for AI Agents
In app/src/main/java/umc/onairmate/ui/pop_up/ChangeNicknamePopup.kt around lines
63 to 69, the current length check uses the raw input so an all-space string can
pass; trim the input first and perform the length check on the trimmed value
(e.g., val nickname = binding.editNickname.text.toString().trim()), use that
trimmed nickname for both validation and subsequent logic, and show the same
Toast and return when the trimmed length is not in 3..10.

Comment on lines +70 to +81
// 외부 콜백 호출해서 중복 검사 요청
onCheckNickname?.invoke(nickname) { isDuplicated ->
if (isDuplicated) {
Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show()
binding.editNickname.text?.clear()
binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
binding.checkNickname.isEnabled = false
} else {
Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show()
}
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

콜백 null 안전성 및 중복 클릭 방지

onCheckNickname이 null이면 아무 동작이 없고, 네트워크 요청 중 연타가 가능합니다. null 가드와 로딩 동안 버튼 비활성화를 권장합니다.

-            // 외부 콜백 호출해서 중복 검사 요청
-            onCheckNickname?.invoke(nickname) { isDuplicated ->
+            val checkNickname = onCheckNickname
+            if (checkNickname == null) {
+                Toast.makeText(requireContext(), "잠시 후 다시 시도해주세요.", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            // 중복 검사 요청 전 클릭 방지
+            binding.checkNickname.isEnabled = false
+            binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
+            // 외부 콜백 호출해서 중복 검사 요청
+            checkNickname.invoke(nickname) { isDuplicated ->
                 if (isDuplicated) {
                     Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show()
                     binding.editNickname.text?.clear()
                     binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
                     binding.checkNickname.isEnabled = false
                 } else {
                     Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show()
+                    // 성공 시 버튼 상태 복구(입력 변경 시에도 TextWatcher가 관리)
+                    binding.checkNickname.isEnabled = true
+                    binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_main)
                 }
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 외부 콜백 호출해서 중복 검사 요청
onCheckNickname?.invoke(nickname) { isDuplicated ->
if (isDuplicated) {
Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show()
binding.editNickname.text?.clear()
binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
binding.checkNickname.isEnabled = false
} else {
Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show()
}
}
val checkNickname = onCheckNickname
if (checkNickname == null) {
Toast.makeText(requireContext(), "잠시 후 다시 시도해주세요.", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
// 중복 검사 요청 전 클릭 방지
binding.checkNickname.isEnabled = false
binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
// 외부 콜백 호출해서 중복 검사 요청
checkNickname.invoke(nickname) { isDuplicated ->
if (isDuplicated) {
Toast.makeText(requireContext(), "이미 사용 중인 닉네임입니다.", Toast.LENGTH_SHORT).show()
binding.editNickname.text?.clear()
binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_disabled)
binding.checkNickname.isEnabled = false
} else {
Toast.makeText(requireContext(), "사용 가능한 닉네임입니다!", Toast.LENGTH_SHORT).show()
// 성공 시 버튼 상태 복구(입력 변경 시에도 TextWatcher가 관리)
binding.checkNickname.isEnabled = true
binding.checkNickname.setBackgroundResource(R.drawable.bg_btn_main)
}
}

Comment on lines +20 to +27
viewModelScope.launch {
try {
val isDuplicated = nicknameRepository.isNicknameDuplicated(nickname)
onResult(isDuplicated)
} catch (e: Exception) {
// 에러 처리 필요하면 여기서
onResult(false) // 중복 아님으로 처리하거나 별도 처리
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

예외 삼킴(detekt): 에러를 기록하고 보수적으로 처리하세요

예외를 무시하고 false를 반환하면 잘못된 승인 가능성이 있습니다. 로깅 추가 및 보수적 처리(중복으로 간주)를 권장합니다.

다음과 같이 수정을 제안합니다:

         viewModelScope.launch {
             try {
-                val isDuplicated = nicknameRepository.isNicknameDuplicated(nickname)
+                val isDuplicated = nicknameRepository.isNicknameDuplicated(nickname.trim())
                 onResult(isDuplicated)
             } catch (e: Exception) {
-                // 에러 처리 필요하면 여기서
-                onResult(false) // 중복 아님으로 처리하거나 별도 처리
+                android.util.Log.e("ChangeNicknameViewModel", "닉네임 중복 확인 실패", e)
+                onResult(true) // 실패 시 보수적으로 중복 처리
             }
         }

추가로, 필요 시 import를 보강하세요(파일 상단):

import android.util.Log
🧰 Tools
🪛 detekt (1.23.8)

[warning] 24-24: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🤖 Prompt for AI Agents
In app/src/main/java/umc/onairmate/ui/pop_up/ChangeNicknameViewModel.kt around
lines 20 to 27, the catch block currently swallows exceptions and returns false
which can allow invalid approval; update the catch to log the exception (e.g.
using Log.e with a clear tag/message) and conservatively treat failures as
duplicated by calling onResult(true) instead of false, and add the import
android.util.Log at the top of the file if missing.

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

Labels

🌟 Feature 기능개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants