-
Notifications
You must be signed in to change notification settings - Fork 1
Feature/온보딩 프로필신고및차단 #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
The head ref may contain hidden characters: "feature/\uC628\uBCF4\uB529-\uD504\uB85C\uD544\uC2E0\uACE0\uBC0F\uCC28\uB2E8"
Conversation
백업용 입니다. 빌드 불가. 에러 투성이 입니다.
백업용 입니다. 빌드 불가. 에러 투성이 입니다.
hilt 부분에서 오류 발생, build 불가
Walkthrough
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: 토스트/버튼 상태 업데이트
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: 콜백 전달
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 unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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.requireContextapp/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
📒 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 등록 일관성 유지 — LGTMRetrofit 기반의 Service 제공 메서드 패턴과 스코프(@singleton)가 기존과 동일하며, 네이밍도 일관적입니다.
app/src/main/java/umc/OnAirMate/data/api/NicknameService.kt (1)
11-16: Retrofit 인터페이스 정의 적절 — LGTMsuspend 함수 시그니처와 Path 바인딩이 명확하며, 반환 타입도 DTO와 일치합니다.
app/src/main/java/umc/onairmate/module/RepositoryModule.kt (2)
52-57: DI 바인딩 스코프와 구현체 연결 적절 — LGTMViewModelScoped 스코프, 생성자 주입 방식이 기존 레이어와 일치합니다.
13-13: NicknameRepository import 경로 확인 완료
NicknameRepository.kt의package가umc.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 | |||
There was a problem hiding this comment.
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
doneLength 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
doneLength 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 | |||
There was a problem hiding this comment.
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
doneLength of output: 483
디렉터리 이름 소문자 일치 필요: OnAirMate → onairmate로 변경
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
- 기존 경로:
- 작업 내용
umc/OnAirMate/…디렉터리 이름을umc/onairmate/…로 변경- 변경된 경로에 맞춰 프로젝트 내 패키지 선언과 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.
| class NicknameResponse ( | ||
| @SerializedName("success") | ||
| val success: Boolean, | ||
|
|
||
| @SerializedName("data") | ||
| val data: NicknameData | ||
| ) No newline at end of file |
There was a problem hiding this comment.
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.
| 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.
| package umc.onairmate.data.repository | ||
|
|
||
| interface NicknameRepository { | ||
| suspend fun isNicknameDuplicated(nickname: String): Boolean | ||
| } No newline at end of file |
There was a problem hiding this comment.
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 -uLength of output: 691
패키지 선언 및 디렉터리 구조 정합성 확보 필요
현재 NicknameRepository.kt 파일이 아래 경로에 위치하면서 패키지 선언(umc.onairmate.data.repository)과 실제 디렉터리 구조가 일치하지 않습니다.
OS나 IDE 인덱싱 이슈를 방지하려면 다음 작업이 필요합니다:
- 대상 파일
- app/src/main/java/umc/OnAirMate/data/repository/repository/NicknameRepository.kt
- 조치 사항
umc/OnAirMate디렉터리명을 기존 테스트 코드(app/src/test/java/umc/onairmate,app/src/androidTest/java/umc/onairmate)와 동일하게 모두 소문자onairmate로 변경- 중복된
repository/repository폴더 구조를 제거하여 최종 경로를
app/src/main/java/umc/onairmate/data/repository/NicknameRepository.kt
형태로 통일 - 변경 후 패키지 선언(
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 | |||
There was a problem hiding this comment.
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 || trueLength 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.
| <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"> | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| android:layout_width="340dp" | ||
| android:layout_height="220dp" |
There was a problem hiding this comment.
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.
| 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.
| android:paddingLeft="20dp" | ||
| android:paddingRight="20dp" | ||
| android:paddingTop="15dp" | ||
| android:paddingBottom="15dp" |
There was a problem hiding this comment.
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.
| 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.
| 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"/> | ||
|
|
There was a problem hiding this comment.
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.
| 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.
| <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"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
버튼 터치 타겟/상태 색상
- 높이 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.
| <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.
There was a problem hiding this 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
📒 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
| <?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"> |
There was a problem hiding this comment.
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.
| <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" /> |
There was a problem hiding this comment.
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.
| <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.
| <!-- 차단 사유 --> | ||
| <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" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <!-- 차단 사유 --> | |
| <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.
| <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" /> |
There was a problem hiding this comment.
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.
| <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.
| <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" /> |
There was a problem hiding this comment.
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.
There was a problem hiding this 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/리뷰 도구에서 경고가 날 수 있습니다. 개행 추가를 권장합니다.
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:layout_width="match_parent" | ||
| android:layout_height="match_parent"> |
There was a problem hiding this comment.
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.
| <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.
약간씩 수정이 되었는데, 올라온 내용 받아와야해서 먼저 커밋 날려놓겠습니다.
There was a problem hiding this 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 위험 있습니다. 메인 스레드 전환 및
_bindingnull 가드를 추가하세요.- 콜백이 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
📒 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: 차단 리스트 네비게이션 연결 LGTMProfile → BlockList 네비게이션 액션 정상 연결되어 있습니다.
| btnClose.setOnClickListener { dialog.dismiss() } | ||
| btnSubmit.setOnClickListener { | ||
| val feedback = etFeedback.text.toString() | ||
| if (feedback.isNotBlank()) { | ||
| // 의견 전송 로직 | ||
| dialog.dismiss() | ||
| } else { | ||
| Toast.makeText(requireContext(), "내용을 입력해주세요", Toast.LENGTH_SHORT).show() | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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="의견 보내기" |
There was a problem hiding this comment.
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.
| 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" /> |
There was a problem hiding this comment.
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.
| 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 하겠습니다. 문제 생길 시 수정하겠습니다.
There was a problem hiding this 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/OnAirMate와umc/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
📒 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> | ||
| } | ||
| } |
There was a problem hiding this comment.
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 2Length 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).
| fun blockFriend(userId: Int) { | ||
| // repository를 통해 서버 요청 | ||
| viewModelScope.launch { | ||
| val response = repository.blockFriend(userId) | ||
| if (response.isSuccessful) { | ||
| result.value = "친구가 차단되었습니다." | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| fun reportFriend(userId: Int) { | ||
| viewModelScope.launch { | ||
| val response = repository.reportFriend(userId) | ||
| if (response.isSuccessful) { | ||
| result.value = "친구가 신고되었습니다." | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| binding.btnMore.setOnClickListener { | ||
| // 더보기 메뉴를 팝업 등으로 처리 | ||
| // 여기서는 예시로 차단/신고 버튼을 바로 실행 | ||
| onBlockClick?.invoke(data) | ||
| onReportClick?.invoke(data) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
차단과 신고 동시 호출 문제
btnMore 클릭 시 onBlockClick과 onReportClick를 모두 호출하고 있습니다. 사용자가 선택할 수 있도록 팝업 메뉴나 다이얼로그를 표시해야 합니다.
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.
| binding.btnMore.setOnClickListener { | |
| // 더보기 메뉴를 팝업 등으로 처리 | |
| // 여기서는 예시로 차단/신고 버튼을 바로 실행 | |
| onBlockClick?.invoke(data) | |
| onReportClick?.invoke(data) | |
| } | |
| binding.btnMore.setOnClickListener { | |
| // 더보기 메뉴 팝업 표시 | |
| showMoreOptionsDialog(data) | |
| } |
app/src/main/java/umc/onairmate/ui/friend/list/FriendListTabFragment.kt
Outdated
Show resolved
Hide resolved
| tools:layout="@layout/fragment_profile"> | ||
| <action | ||
| android:id="@+id/action_profileFragment_to_blockListFragment" | ||
| app:destination="@id/blockListFragment" /> | ||
| </fragment> |
There was a problem hiding this comment.
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"
blockListFragmentdestination ID도 nav_graph.xml (59, 63), mobile_navigation.xml (47, 51), fragment_block_list.xml (7)에서 중복 정의
- app/src/main/res/navigation/nav_graph.xml, mobile_navigation.xml:
-
제안하는 일관된 네이밍 (
navigation_접두어 사용)- Action ID
- @+id/action_profileFragment_to_blockListFragment + @+id/navigation_action_profile_to_block_list
- Destination ID
- @+id/blockListFragment + @+id/navigation_block_list
- Action ID
-
변경 시 함께 업데이트할 참조
- 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속성
- ProfileFragment.kt (84행)
🤖 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.
| <fragment | ||
| android:id="@+id/blockListFragment" | ||
| android:name="umc.onairmate.ui.profile.BlockListFragment" | ||
| tools:layout="@layout/fragment_block_list" /> |
There was a problem hiding this comment.
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.ktLength 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.
There was a problem hiding this 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.
📒 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, 아니라면 클릭 후 닫힘 처리를 보장하세요.
| suspend fun blockFriend(accessToken: Int): DefaultResponse<Unit> | ||
| suspend fun reportFriend(accessToken: Int, userId: Int): DefaultResponse<Unit> | ||
| abstract fun reportFriend(accessToken: Int): DefaultResponse<Unit> |
There was a problem hiding this comment.
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사용(일관성 붕괴). blockFriend에userId누락.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 3Length 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.
| 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.
| 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") | ||
| } |
There was a problem hiding this comment.
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로 선언됨(일관성 저하 및 컴파일 타임 오류 유발 가능).
blockFriend에userId인자가 없어 실제 차단 대상 지정 불가.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" -SLength 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 || trueLength 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.
| 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) } | |
| } |
| fun onItemClick(friendData: FriendData) | ||
| fun onRequestItemClick(requestedData: RequestedFriendData) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
추가된 인터페이스 메서드로 인한 호환성(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.
| 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.
| 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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
생성자 콜백 람다가 저장/전달되지 않아 동작하지 않습니다.
- 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.
| private val items = mutableListOf<Any>() | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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() | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
문자열 비교로 분기하는 로직은 취약하며 friendId가 설정되지 않습니다.
- "친구가 삭제되었습니다." 같은 문자열로 분기하는 방식은 변경에 취약합니다.
- friendId는 어디에서도 설정되지 않아 0으로 전달될 가능성이 큽니다.
권장:
- ViewModel의 결과를 문자열이 아닌 이벤트(예: sealed class)로 분리하세요.
- 삭제 수행 시점에 friendId를 설정하고, 이벤트에 포함하여 전달하세요.
예시(개념):
- FriendViewModel.result 대신 LiveData 사용
- FriendEvent.FriendDeleted(friendId), FriendEvent.Message(text) 등으로 구분
| 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") | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
3cb310e to
d4a2755
Compare
There was a problem hiding this 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.
📒 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.availableapp/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 범위와 시그니처가 기존 패턴과 일치합니다.
| 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 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
에러를 ‘중복 아님(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.
| 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.
| private inline fun <reified T> Retrofit.buildService(): T{ | ||
| return this.create(T::class.java) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
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 ...buildService 후 retrofit.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.
| 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 1Length 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"
fiLength 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.
| import umc.onairmate.data.api.NicknameService | ||
| import umc.onairmate.data.repository.NicknameRepository | ||
| import umc.onairmate.data.repository.repository.FriendRepository |
There was a problem hiding this comment.
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 3Length 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).
| val nickname = binding.editNickname.text.toString() | ||
|
|
||
| if (nickname.length !in 3..10) { | ||
| Toast.makeText(requireContext(), "닉네임은 3자 이상 10자 이하여야 합니다.", Toast.LENGTH_SHORT).show() | ||
| return@setOnClickListener | ||
| } | ||
|
|
There was a problem hiding this comment.
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.
| 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.
| // 외부 콜백 호출해서 중복 검사 요청 | ||
| 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() | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
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.
| // 외부 콜백 호출해서 중복 검사 요청 | |
| 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) | |
| } | |
| } |
| viewModelScope.launch { | ||
| try { | ||
| val isDuplicated = nicknameRepository.isNicknameDuplicated(nickname) | ||
| onResult(isDuplicated) | ||
| } catch (e: Exception) { | ||
| // 에러 처리 필요하면 여기서 | ||
| onResult(false) // 중복 아님으로 처리하거나 별도 처리 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
예외 삼킴(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.
체크리스트
📝작업 내용
이번 PR에서 작업한 내용을 간략히 설명해주세요
프로필 화면에서 차단 리스트 이동 구현
스크린샷
작업한 화면기능의 스크린샷을 첨부해 주세요
💬리뷰 요구사항(선택)
리뷰어가 특별히 봐주었으면 하는 부분이나 함께 고민해봐야할 부분이 있다면 작성해주세요
Summary by CodeRabbit